aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib')
-rw-r--r--actionview/lib/action_view.rb8
-rw-r--r--actionview/lib/action_view/base.rb36
-rw-r--r--actionview/lib/action_view/buffers.rb2
-rw-r--r--actionview/lib/action_view/dependency_tracker.rb9
-rw-r--r--actionview/lib/action_view/digestor.rb168
-rw-r--r--actionview/lib/action_view/flows.rb18
-rw-r--r--actionview/lib/action_view/gem_version.rb4
-rw-r--r--actionview/lib/action_view/helpers.rb2
-rw-r--r--actionview/lib/action_view/helpers/active_model_helper.rb16
-rw-r--r--actionview/lib/action_view/helpers/asset_tag_helper.rb67
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb142
-rw-r--r--actionview/lib/action_view/helpers/atom_feed_helper.rb21
-rw-r--r--actionview/lib/action_view/helpers/cache_helper.rb73
-rw-r--r--actionview/lib/action_view/helpers/capture_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/controller_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/csrf_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/date_helper.rb215
-rw-r--r--actionview/lib/action_view/helpers/debug_helper.rb5
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb114
-rw-r--r--actionview/lib/action_view/helpers/form_options_helper.rb33
-rw-r--r--actionview/lib/action_view/helpers/form_tag_helper.rb50
-rw-r--r--actionview/lib/action_view/helpers/javascript_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/number_helper.rb95
-rw-r--r--actionview/lib/action_view/helpers/output_safety_helper.rb32
-rw-r--r--actionview/lib/action_view/helpers/rendering_helper.rb4
-rw-r--r--actionview/lib/action_view/helpers/sanitize_helper.rb6
-rw-r--r--actionview/lib/action_view/helpers/tag_helper.rb270
-rw-r--r--actionview/lib/action_view/helpers/tags/base.rb198
-rw-r--r--actionview/lib/action_view/helpers/tags/check_box.rb34
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_check_boxes.rb14
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_helpers.rb118
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/collection_select.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/date_select.rb70
-rw-r--r--actionview/lib/action_view/helpers/tags/datetime_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/file_field.rb15
-rw-r--r--actionview/lib/action_view/helpers/tags/grouped_collection_select.rb4
-rw-r--r--actionview/lib/action_view/helpers/tags/password_field.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/radio_button.rb8
-rw-r--r--actionview/lib/action_view/helpers/tags/select.rb18
-rw-r--r--actionview/lib/action_view/helpers/tags/text_area.rb2
-rw-r--r--actionview/lib/action_view/helpers/tags/text_field.rb8
-rw-r--r--actionview/lib/action_view/helpers/tags/translator.rb24
-rw-r--r--actionview/lib/action_view/helpers/text_helper.rb44
-rw-r--r--actionview/lib/action_view/helpers/translation_helper.rb12
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb118
-rw-r--r--actionview/lib/action_view/layouts.rb70
-rw-r--r--actionview/lib/action_view/log_subscriber.rb59
-rw-r--r--actionview/lib/action_view/lookup_context.rb57
-rw-r--r--actionview/lib/action_view/path_set.rb38
-rw-r--r--actionview/lib/action_view/railtie.rb14
-rw-r--r--actionview/lib/action_view/record_identifier.rb8
-rw-r--r--actionview/lib/action_view/renderer/abstract_renderer.rb32
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb378
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb38
-rw-r--r--actionview/lib/action_view/renderer/streaming_template_renderer.rb92
-rw-r--r--actionview/lib/action_view/renderer/template_renderer.rb130
-rw-r--r--actionview/lib/action_view/routing_url_for.rb3
-rw-r--r--actionview/lib/action_view/tasks/cache_digests.rake23
-rw-r--r--actionview/lib/action_view/tasks/dependencies.rake23
-rw-r--r--actionview/lib/action_view/template.rb59
-rw-r--r--actionview/lib/action_view/template/error.rb24
-rw-r--r--actionview/lib/action_view/template/handlers.rb8
-rw-r--r--actionview/lib/action_view/template/handlers/builder.rb10
-rw-r--r--actionview/lib/action_view/template/handlers/erb.rb47
-rw-r--r--actionview/lib/action_view/template/handlers/raw.rb2
-rw-r--r--actionview/lib/action_view/template/html.rb6
-rw-r--r--actionview/lib/action_view/template/resolver.rb193
-rw-r--r--actionview/lib/action_view/template/text.rb6
-rw-r--r--actionview/lib/action_view/template/types.rb3
-rw-r--r--actionview/lib/action_view/test_case.rb24
-rw-r--r--actionview/lib/action_view/testing/resolvers.rb21
-rw-r--r--actionview/lib/action_view/version.rb2
-rw-r--r--actionview/lib/action_view/view_paths.rb18
74 files changed, 1873 insertions, 1628 deletions
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb
index 0a87500a52..ba6755be82 100644
--- a/actionview/lib/action_view.rb
+++ b/actionview/lib/action_view.rb
@@ -21,9 +21,9 @@
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#++
-require 'active_support'
-require 'active_support/rails'
-require 'action_view/version'
+require "active_support"
+require "active_support/rails"
+require "action_view/version"
module ActionView
extend ActiveSupport::Autoload
@@ -89,7 +89,7 @@ module ActionView
end
end
-require 'active_support/core_ext/string/output_safety'
+require "active_support/core_ext/string/output_safety"
ActiveSupport.on_load(:i18n) do
I18n.load_path << "#{File.dirname(__FILE__)}/action_view/locale/en.yml"
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index ad1cb1a4be..b7c05fdb88 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -1,11 +1,11 @@
-require 'active_support/core_ext/module/attr_internal'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/ordered_options'
-require 'action_view/log_subscriber'
-require 'action_view/helpers'
-require 'action_view/context'
-require 'action_view/template'
-require 'action_view/lookup_context'
+require "active_support/core_ext/module/attr_internal"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/ordered_options"
+require "action_view/log_subscriber"
+require "action_view/helpers"
+require "action_view/context"
+require "action_view/template"
+require "action_view/lookup_context"
module ActionView #:nodoc:
# = Action View Base
@@ -17,7 +17,7 @@ module ActionView #:nodoc:
#
# == ERB
#
- # You trigger ERB by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
+ # You trigger ERB by using embeddings such as <tt><% %></tt>, <tt><% -%></tt>, and <tt><%= %></tt>. The <tt><%= %></tt> tag set is used when you want output. Consider the
# following loop for names:
#
# <b>Names of all the people</b>
@@ -25,7 +25,7 @@ module ActionView #:nodoc:
# Name: <%= person.name %><br/>
# <% end %>
#
- # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this
+ # The loop is setup in regular embedding tags <tt><% %></tt>, and the name is written using the output embedding tag <tt><%= %></tt>. Note that this
# is not just a usage suggestion. Regular output functions like print or puts won't work with ERB templates. So this would be wrong:
#
# <%# WRONG %>
@@ -33,9 +33,9 @@ module ActionView #:nodoc:
#
# If you absolutely must write from within a function use +concat+.
#
- # When on a line that only contains whitespaces except for the tag, <% %> suppress leading and trailing whitespace,
- # including the trailing newline. <% %> and <%- -%> are the same.
- # Note however that <%= %> and <%= -%> are different: only the latter removes trailing whitespaces.
+ # When on a line that only contains whitespaces except for the tag, <tt><% %></tt> suppresses leading and trailing whitespace,
+ # including the trailing newline. <tt><% %></tt> and <tt><%- -%></tt> are the same.
+ # Note however that <tt><%= %></tt> and <tt><%= -%></tt> are different: only the latter removes trailing whitespaces.
#
# === Using sub templates
#
@@ -110,7 +110,7 @@ module ActionView #:nodoc:
# <p>A product of Danish Design during the Winter of '79...</p>
# </div>
#
- # A full-length RSS example actually used on Basecamp:
+ # Here is a full-length RSS example actually used on Basecamp:
#
# xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do
# xml.channel do
@@ -141,7 +141,7 @@ module ActionView #:nodoc:
# 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 }
+ @@field_error_proc = Proc.new { |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe }
# How to complete the streaming when an exception occurs.
# This is our best guess: first try to close the attribute, then the tag.
@@ -169,7 +169,7 @@ module ActionView #:nodoc:
class_attribute :logger
class << self
- delegate :erb_trim_mode=, :to => 'ActionView::Template::Handlers::ERB'
+ delegate :erb_trim_mode=, to: "ActionView::Template::Handlers::ERB"
def cache_template_loading
ActionView::Resolver.caching?
@@ -187,8 +187,8 @@ module ActionView #:nodoc:
attr_accessor :view_renderer
attr_internal :config, :assigns
- delegate :lookup_context, :to => :view_renderer
- delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context
+ delegate :lookup_context, to: :view_renderer
+ delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, to: :lookup_context
def assign(new_assigns) # :nodoc:
@_assigns = new_assigns.each { |key, value| instance_variable_set("@#{key}", value) }
diff --git a/actionview/lib/action_view/buffers.rb b/actionview/lib/action_view/buffers.rb
index be5d86b1dc..089daa6d60 100644
--- a/actionview/lib/action_view/buffers.rb
+++ b/actionview/lib/action_view/buffers.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/string/output_safety'
+require "active_support/core_ext/string/output_safety"
module ActionView
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb
index 7731773040..451eeec9d6 100644
--- a/actionview/lib/action_view/dependency_tracker.rb
+++ b/actionview/lib/action_view/dependency_tracker.rb
@@ -1,5 +1,5 @@
-require 'concurrent/map'
-require 'action_view/path_set'
+require "concurrent/map"
+require "action_view/path_set"
module ActionView
class DependencyTracker # :nodoc:
@@ -105,7 +105,6 @@ module ActionView
attr_reader :name, :template
private :name, :template
-
private
def source
template.source
@@ -142,7 +141,7 @@ module ActionView
def add_static_dependency(dependencies, dependency)
if dependency
- if dependency.include?('/')
+ if dependency.include?("/")
dependencies << dependency
else
dependencies << "#{directory}/#{dependency}"
@@ -163,7 +162,7 @@ module ActionView
def explicit_dependencies
dependencies = source.scan(EXPLICIT_DEPENDENCY).flatten.uniq
- wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == '*' }
+ wildcards, explicits = dependencies.partition { |dependency| dependency[-1] == "*" }
(explicits + resolve_directories(wildcards)).uniq
end
diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb
index 657026fa14..0658d8601d 100644
--- a/actionview/lib/action_view/digestor.rb
+++ b/actionview/lib/action_view/digestor.rb
@@ -1,17 +1,14 @@
-require 'concurrent/map'
-require 'action_view/dependency_tracker'
-require 'monitor'
+require "concurrent/map"
+require "action_view/dependency_tracker"
+require "monitor"
module ActionView
class Digestor
- cattr_reader(:cache)
- @@cache = Concurrent::Map.new
- @@digest_monitor = Monitor.new
-
- class PerRequestDigestCacheExpiry < Struct.new(:app) # :nodoc:
- def call(env)
- ActionView::Digestor.cache.clear
- app.call(env)
+ @@digest_mutex = Mutex.new
+
+ module PerExecutionDigestCacheExpiry
+ def self.before(target)
+ ActionView::LookupContext::DetailsKey.clear
end
end
@@ -21,112 +18,107 @@ 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
- # * <tt>partial</tt> - Specifies whether the template is a partial
- def digest(name:, finder:, **options)
- options.assert_valid_keys(:dependencies, :partial)
-
- cache_key = ([ name, finder.details_key.hash ].compact + Array.wrap(options[:dependencies])).join('.')
+ def digest(name:, finder:, dependencies: [])
+ dependencies ||= []
+ cache_key = [ name, finder.rendered_format, dependencies ].flatten.compact.join(".")
# this is a correctly done double-checked locking idiom
# (Concurrent::Map's lookups have volatile semantics)
- @@cache[cache_key] || @@digest_monitor.synchronize do
- @@cache.fetch(cache_key) do # re-check under lock
- compute_and_store_digest(cache_key, name, finder, options)
+ finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
+ finder.digest_cache.fetch(cache_key) do # re-check under lock
+ partial = name.include?("/_")
+ root = tree(name, finder, partial)
+ dependencies.each do |injected_dep|
+ root.children << Injected.new(injected_dep, nil, nil)
+ end
+ finder.digest_cache[cache_key] = root.digest(finder)
end
end
end
- private
- def compute_and_store_digest(cache_key, name, finder, options) # called under @@digest_monitor lock
- klass = if options[:partial] || name.include?("/_")
- # Prevent re-entry or else recursive templates will blow the stack.
- # There is no need to worry about other threads seeing the +false+ value,
- # as they will then have to wait for this thread to let go of the @@digest_monitor lock.
- pre_stored = @@cache.put_if_absent(cache_key, false).nil? # put_if_absent returns nil on insertion
- PartialDigestor
- else
- Digestor
- end
-
- @@cache[cache_key] = stored_digest = klass.new(name, finder, options).digest
- ensure
- # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache
- @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest
- end
- end
+ def logger
+ ActionView::Base.logger || NullLogger
+ end
- attr_reader :name, :finder, :options
+ # Create a dependency tree for template named +name+.
+ def tree(name, finder, partial = false, seen = {})
+ logical_name = name.gsub(%r|/_|, "/")
- def initialize(name, finder, options = {})
- @name, @finder = name, finder
- @options = options
- end
+ options = {}
+ options[:formats] = [finder.rendered_format] if finder.rendered_format
- def digest
- Digest::MD5.hexdigest("#{source}-#{dependency_digest}").tap do |digest|
- logger.debug " Cache digest for #{template.inspect}: #{digest}"
- end
- rescue ActionView::MissingTemplate
- logger.error " Couldn't find template for digesting: #{name}"
- ''
- end
+ if template = finder.disable_cache { finder.find_all(logical_name, [], partial, [], options).first }
+ finder.rendered_format ||= template.formats.first
- def dependencies
- DependencyTracker.find_dependencies(name, template, finder.view_paths)
- rescue ActionView::MissingTemplate
- logger.error " '#{name}' file doesn't exist, so no dependencies"
- []
- end
+ if node = seen[template.identifier] # handle cycles in the tree
+ node
+ else
+ node = seen[template.identifier] = Node.create(name, logical_name, template, partial)
- def nested_dependencies
- dependencies.collect do |dependency|
- dependencies = PartialDigestor.new(dependency, finder).nested_dependencies
- dependencies.any? ? { dependency => dependencies } : dependency
+ deps = DependencyTracker.find_dependencies(name, template, finder.view_paths)
+ deps.uniq { |n| n.gsub(%r|/_|, "/") }.each do |dep_file|
+ node.children << tree(dep_file, finder, true, seen)
+ end
+ node
+ end
+ else
+ logger.error " '#{name}' file doesn't exist, so no dependencies"
+ logger.error " Couldn't find template for digesting: #{name}"
+ seen[name] ||= Missing.new(name, logical_name, nil)
+ end
end
end
- private
- class NullLogger
- def self.debug(_); end
- def self.error(_); end
- end
+ class Node
+ attr_reader :name, :logical_name, :template, :children
- def logger
- ActionView::Base.logger || NullLogger
+ def self.create(name, logical_name, template, partial)
+ klass = partial ? Partial : Node
+ klass.new(name, logical_name, template, [])
end
- def logical_name
- name.gsub(%r|/_|, "/")
+ def initialize(name, logical_name, template, children = [])
+ @name = name
+ @logical_name = logical_name
+ @template = template
+ @children = children
end
- def partial?
- false
+ def digest(finder, stack = [])
+ Digest::MD5.hexdigest("#{template.source}-#{dependency_digest(finder, stack)}")
end
- def template
- @template ||= finder.disable_cache { finder.find(logical_name, [], partial?) }
+ def dependency_digest(finder, stack)
+ children.map do |node|
+ if stack.include?(node)
+ false
+ else
+ finder.digest_cache[node.name] ||= begin
+ stack.push node
+ node.digest(finder, stack).tap { stack.pop }
+ end
+ end
+ end.join("-")
end
- def source
- template.source
+ def to_dep_map
+ children.any? ? { name => children.map(&:to_dep_map) } : name
end
+ end
- def dependency_digest
- template_digests = dependencies.collect do |template_name|
- Digestor.digest(name: template_name, finder: finder, partial: true)
- end
+ class Partial < Node; end
- (template_digests + injected_dependencies).join("-")
- end
+ class Missing < Node
+ def digest(finder, _ = []) "" end
+ end
- def injected_dependencies
- Array.wrap(options[:dependencies])
- end
- end
+ class Injected < Node
+ def digest(finder, _ = []) name end
+ end
- class PartialDigestor < Digestor # :nodoc:
- def partial?
- true
+ class NullLogger
+ def self.debug(_); end
+ def self.error(_); end
end
end
end
diff --git a/actionview/lib/action_view/flows.rb b/actionview/lib/action_view/flows.rb
index bc61920848..16874c1194 100644
--- a/actionview/lib/action_view/flows.rb
+++ b/actionview/lib/action_view/flows.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/string/output_safety'
+require "active_support/core_ext/string/output_safety"
module ActionView
class OutputFlow #:nodoc:
@@ -23,7 +23,6 @@ module ActionView
@content[key] << value
end
alias_method :append!, :append
-
end
class StreamingFlow < OutputFlow #:nodoc:
@@ -37,9 +36,8 @@ module ActionView
end
# Try to get stored content. If the content
- # is not available and we are inside the layout
- # fiber, we set that we are waiting for the given
- # key and yield.
+ # is not available and we're inside the layout fiber,
+ # then it will begin waiting for the given key and yield.
def get(key)
return super if @content.key?(key)
@@ -60,8 +58,8 @@ module ActionView
end
# Appends the contents for the given key. This is called
- # by provides and resumes back to the fiber if it is
- # the key it is waiting for.
+ # by providing and resuming back to the fiber,
+ # if that's the key it's waiting for.
def append!(key, value)
super
@fiber.resume if @waiting_for == key
@@ -69,8 +67,8 @@ module ActionView
private
- def inside_fiber?
- Fiber.current.object_id != @root
- end
+ def inside_fiber?
+ Fiber.current.object_id != @root
+ end
end
end
diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb
index bb5c96cb39..5fc4f3f1b9 100644
--- a/actionview/lib/action_view/gem_version.rb
+++ b/actionview/lib/action_view/gem_version.rb
@@ -6,9 +6,9 @@ module ActionView
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta2"
+ 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 787e9d67b2..c1b4b4f84b 100644
--- a/actionview/lib/action_view/helpers.rb
+++ b/actionview/lib/action_view/helpers.rb
@@ -1,4 +1,4 @@
-require 'active_support/benchmarkable'
+require "active_support/benchmarkable"
module ActionView #:nodoc:
module Helpers #:nodoc:
diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb
index d5222e3616..4bb5788a16 100644
--- a/actionview/lib/action_view/helpers/active_model_helper.rb
+++ b/actionview/lib/action_view/helpers/active_model_helper.rb
@@ -1,5 +1,5 @@
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/enumerable'
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/enumerable"
module ActionView
# = Active Model Helpers
@@ -37,13 +37,13 @@ module ActionView
private
- def object_has_errors?
- object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
- end
+ def object_has_errors?
+ object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present?
+ end
- def tag_generate_errors?(options)
- options['type'] != 'hidden'
- end
+ def tag_generate_errors?(options)
+ options["type"] != "hidden"
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb
index 413c35954c..39fa11ccfd 100644
--- a/actionview/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb
@@ -1,7 +1,7 @@
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/hash/keys'
-require 'action_view/helpers/asset_url_helper'
-require 'action_view/helpers/tag_helper'
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/hash/keys"
+require "action_view/helpers/asset_url_helper"
+require "action_view/helpers/tag_helper"
module ActionView
# = Action View Asset Tag Helpers
@@ -55,7 +55,7 @@ module ActionView
# # => <script src="http://www.example.com/xmlhr.js"></script>
def javascript_include_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!('protocol', 'extname', 'host').symbolize_keys
+ path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
tag_options = {
"src" => path_to_javascript(source, path_options)
@@ -91,8 +91,7 @@ module ActionView
# # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
def stylesheet_link_tag(*sources)
options = sources.extract_options!.stringify_keys
- path_options = options.extract!('protocol', 'host').symbolize_keys
-
+ path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
sources.uniq.map { |source|
tag_options = {
"rel" => "stylesheet",
@@ -138,7 +137,7 @@ module ActionView
"rel" => tag_options[:rel] || "alternate",
"type" => tag_options[:type] || Template::Types[type].to_s,
"title" => tag_options[:title] || type.to_s.upcase,
- "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(:only_path => false)) : url_options
+ "href" => url_options.is_a?(Hash) ? url_for(url_options.merge(only_path: false)) : url_options
)
end
@@ -169,11 +168,11 @@ module ActionView
#
# favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png'
# # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" />
- def favicon_link_tag(source='favicon.ico', options={})
- tag('link', {
- :rel => 'shortcut icon',
- :type => 'image/x-icon',
- :href => path_to_image(source)
+ def favicon_link_tag(source="favicon.ico", options={})
+ tag("link", {
+ rel: "shortcut icon",
+ type: "image/x-icon",
+ href: path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
}.merge!(options.symbolize_keys))
end
@@ -211,10 +210,10 @@ module ActionView
options = options.symbolize_keys
check_for_image_tag_errors(options)
- src = options[:src] = path_to_image(source)
+ src = options[:src] = path_to_image(source, skip_pipeline: options.delete(:skip_pipeline))
- unless src =~ /^(?:cid|data):/ || src.blank?
- options[:alt] = options.fetch(:alt){ image_alt(src) }
+ unless src.start_with?("cid:") || src.start_with?("data:") || src.blank?
+ options[:alt] = options.fetch(:alt) { image_alt(src) }
end
options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
@@ -239,7 +238,7 @@ module ActionView
# image_alt('underscored_file_name.png')
# # => Underscored file name
def image_alt(src)
- File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32,64}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize
+ 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,
@@ -257,6 +256,8 @@ module ActionView
# * <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>:poster_skip_pipeline</tt> will bypass the asset pipeline when using
+ # the <tt>:poster</tt> option instead using an asset in the public folder.
#
# ==== Examples
#
@@ -264,10 +265,12 @@ module ActionView
# # => <video src="/videos/trailer"></video>
# video_tag("trailer.ogg")
# # => <video src="/videos/trailer.ogg"></video>
- # video_tag("trailer.ogg", controls: true, autobuffer: true)
- # # => <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" ></video>
+ # video_tag("trailer.ogg", controls: true, preload: 'none')
+ # # => <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)
+ # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="screenshot.png"></video>
# video_tag("/trailers/hd.avi", size: "16x16")
# # => <video src="/trailers/hd.avi" width="16" height="16"></video>
# video_tag("/trailers/hd.avi", size: "16")
@@ -281,9 +284,12 @@ module ActionView
# video_tag(["trailer.ogg", "trailer.flv"], size: "160x120")
# # => <video height="120" width="160"><source src="/videos/trailer.ogg" /><source src="/videos/trailer.flv" /></video>
def video_tag(*sources)
- multiple_sources_tag('video', sources) do |options|
- options[:poster] = path_to_image(options[:poster]) if options[:poster]
- options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
+ options = sources.extract_options!.symbolize_keys
+ public_poster_folder = options.delete(:poster_skip_pipeline)
+ sources << options
+ multiple_sources_tag_builder("video", sources) do |tag_options|
+ tag_options[:poster] = path_to_image(tag_options[:poster], skip_pipeline: public_poster_folder) if tag_options[:poster]
+ tag_options[:width], tag_options[:height] = extract_dimensions(tag_options.delete(:size)) if tag_options[:size]
end
end
@@ -300,31 +306,32 @@ module ActionView
# audio_tag("sound.wav", "sound.mid")
# # => <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio>
def audio_tag(*sources)
- multiple_sources_tag('audio', sources)
+ multiple_sources_tag_builder("audio", sources)
end
private
- def multiple_sources_tag(type, sources)
- options = sources.extract_options!.symbolize_keys
+ def multiple_sources_tag_builder(type, sources)
+ options = sources.extract_options!.symbolize_keys
+ skip_pipeline = options.delete(:skip_pipeline)
sources.flatten!
yield options if block_given?
if sources.size > 1
content_tag(type, options) do
- safe_join sources.map { |source| tag("source", :src => send("path_to_#{type}", source)) }
+ safe_join sources.map { |source| tag("source", src: send("path_to_#{type}", source, skip_pipeline: skip_pipeline)) }
end
else
- options[:src] = send("path_to_#{type}", sources.first)
+ options[:src] = send("path_to_#{type}", sources.first, skip_pipeline: skip_pipeline)
content_tag(type, nil, options)
end
end
def extract_dimensions(size)
size = size.to_s
- if size =~ %r{\A\d+x\d+\z}
- size.split('x')
- elsif size =~ %r{\A\d+\z}
+ if /\A\d+x\d+\z/.match?(size)
+ size.split("x")
+ elsif /\A\d+\z/.match?(size)
[size, size]
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 717b326740..fab49e402b 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -1,4 +1,4 @@
-require 'zlib'
+require "zlib"
module ActionView
# = Action View Asset URL Helpers
@@ -36,7 +36,7 @@ module ActionView
# some asset downloads to wait for previous assets to finish before they can
# begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to
# distribute the requests over four hosts. For example,
- # <tt>assets%d.example.com<tt> will spread the asset requests over
+ # <tt>assets%d.example.com</tt> will spread the asset requests over
# "assets0.example.com", ..., "assets3.example.com".
#
# image_tag("rails.png")
@@ -117,31 +117,86 @@ module ActionView
module AssetUrlHelper
URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//}i
- # Computes the path to asset in public directory. If :type
- # options is set, a file extension will be appended and scoped
- # to the corresponding public directory.
+ # This is the entry point for all assets.
+ # When using the asset pipeline (i.e. sprockets and sprockets-rails), the
+ # behavior is "enhanced". You can bypass the asset pipeline by passing in
+ # <tt>skip_pipeline: true</tt> to the options.
#
# All other asset *_path helpers delegate through this method.
#
- # asset_path "application.js" # => /assets/application.js
- # asset_path "application", type: :javascript # => /assets/application.js
- # asset_path "application", type: :stylesheet # => /assets/application.css
- # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
+ # === With the asset pipeline
+ #
+ # All options passed to +asset_path+ will be passed to +compute_asset_path+
+ # which is implemented by sprockets-rails.
+ #
+ # asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
+ #
+ # === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
+ #
+ # Accepts a <tt>type</tt> option that can specify the asset's extension. No error
+ # checking is done to verify the source passed into +asset_path+ is valid
+ # and that the file exists on disk.
+ #
+ # asset_path("application.js", skip_pipeline: true) # => "application.js"
+ # asset_path("filedoesnotexist.png", skip_pipeline: true) # => "filedoesnotexist.png"
+ # asset_path("application", type: :javascript, skip_pipeline: true) # => "/javascripts/application.js"
+ # asset_path("application", type: :stylesheet, skip_pipeline: true) # => "/stylesheets/application.css"
+ #
+ # === Options applying to all assets
+ #
+ # 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
+ # 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.
+ #
+ # asset_path("/foo.png") # => "/foo.png"
+ #
+ # - All blank strings will be returned immediately. This bypasses the
+ # asset pipeline and all other behavior described.
+ #
+ # asset_path("") # => ""
+ #
+ # - If <tt>config.relative_url_root</tt> is specified, all assets will have that
+ # root prepended.
+ #
+ # Rails.application.config.relative_url_root = "bar"
+ # asset_path("foo.js", skip_pipeline: true) # => "bar/foo.js"
+ #
+ # - A different asset host can be specified via <tt>config.action_controller.asset_host</tt>
+ # this is commonly used in conjunction with a CDN.
+ #
+ # Rails.application.config.action_controller.asset_host = "assets.example.com"
+ # asset_path("foo.js", skip_pipeline: true) # => "http://assets.example.com/foo.js"
+ #
+ # - An extension name can be specified manually with <tt>extname</tt>.
+ #
+ # asset_path("foo", skip_pipeline: true, extname: ".js") # => "/foo.js"
+ # asset_path("foo.css", skip_pipeline: true, extname: ".js") # => "/foo.css.js"
def asset_path(source, options = {})
raise ArgumentError, "nil is not a valid asset source" if source.nil?
source = source.to_s
- return "" unless source.present?
- return source if source =~ URI_REGEXP
+ return "" if source.blank?
+ return source if URI_REGEXP.match?(source)
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze)
+ tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "".freeze)
if extname = compute_asset_extname(source, options)
source = "#{source}#{extname}"
end
if source[0] != ?/
- source = compute_asset_path(source, options)
+ if options[:skip_pipeline]
+ source = public_compute_asset_path(source, options)
+ else
+ source = compute_asset_path(source, options)
+ end
end
relative_url_root = defined?(config.relative_url_root) && config.relative_url_root
@@ -168,13 +223,13 @@ module ActionView
# asset_url "application.js", host: "http://cdn.example.com" # => http://cdn.example.com/assets/application.js
#
def asset_url(source, options = {})
- path_to_asset(source, options.merge(:protocol => :request))
+ path_to_asset(source, options.merge(protocol: :request))
end
alias_method :url_to_asset, :asset_url # aliased to avoid conflicts with an asset_url named route
ASSET_EXTENSIONS = {
- javascript: '.js',
- stylesheet: '.css'
+ javascript: ".js",
+ stylesheet: ".css"
}
# Compute extname to append to asset path. Returns nil if
@@ -187,12 +242,12 @@ module ActionView
# Maps asset types to public directory.
ASSET_PUBLIC_DIRECTORIES = {
- audio: '/audios',
- font: '/fonts',
- image: '/images',
- javascript: '/javascripts',
- stylesheet: '/stylesheets',
- video: '/videos'
+ audio: "/audios",
+ font: "/fonts",
+ image: "/images",
+ javascript: "/javascripts",
+ stylesheet: "/stylesheets",
+ video: "/videos"
}
# Computes asset path to public directory. Plugins and
@@ -202,6 +257,7 @@ module ActionView
dir = ASSET_PUBLIC_DIRECTORIES[options[:type]] || ""
File.join(dir, source)
end
+ alias :public_compute_asset_path :compute_asset_path
# Pick an asset host for this source. Returns +nil+ if no host is set,
# the host if no wildcard is set, the host interpolated with the
@@ -213,19 +269,21 @@ module ActionView
host = options[:host]
host ||= config.asset_host if defined? config.asset_host
- if host.respond_to?(:call)
- arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
- args = [source]
- args << request if request && (arity > 1 || arity < 0)
- host = host.call(*args)
- elsif host =~ /%d/
- host = host % (Zlib.crc32(source) % 4)
+ if host
+ if host.respond_to?(:call)
+ arity = host.respond_to?(:arity) ? host.arity : host.method(:call).arity
+ args = [source]
+ args << request if request && (arity > 1 || arity < 0)
+ host = host.call(*args)
+ elsif host.include?("%d")
+ host = host % (Zlib.crc32(source) % 4)
+ end
end
host ||= request.base_url if request && options[:protocol] == :request
return unless host
- if host =~ URI_REGEXP
+ if URI_REGEXP.match?(host)
host
else
protocol = options[:protocol] || config.default_asset_host_protocol || (request ? :request : :relative)
@@ -251,7 +309,7 @@ module ActionView
# javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr
# javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js
def javascript_path(source, options = {})
- path_to_asset(source, {type: :javascript}.merge!(options))
+ path_to_asset(source, { type: :javascript }.merge!(options))
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
@@ -263,7 +321,7 @@ module ActionView
# javascript_url "js/xmlhr.js", host: "http://stage.example.com" # => http://stage.example.com/assets/dir/xmlhr.js
#
def javascript_url(source, options = {})
- url_to_asset(source, {type: :javascript}.merge!(options))
+ url_to_asset(source, { type: :javascript }.merge!(options))
end
alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route
@@ -278,7 +336,7 @@ module ActionView
# stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style
# stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css
def stylesheet_path(source, options = {})
- path_to_asset(source, {type: :stylesheet}.merge!(options))
+ path_to_asset(source, { type: :stylesheet }.merge!(options))
end
alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route
@@ -290,7 +348,7 @@ module ActionView
# stylesheet_url "css/style.css", host: "http://stage.example.com" # => http://stage.example.com/css/style.css
#
def stylesheet_url(source, options = {})
- url_to_asset(source, {type: :stylesheet}.merge!(options))
+ url_to_asset(source, { type: :stylesheet }.merge!(options))
end
alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route
@@ -308,7 +366,7 @@ module ActionView
# The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and
# plugin authors are encouraged to do so.
def image_path(source, options = {})
- path_to_asset(source, {type: :image}.merge!(options))
+ path_to_asset(source, { type: :image }.merge!(options))
end
alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route
@@ -320,7 +378,7 @@ module ActionView
# image_url "edit.png", host: "http://stage.example.com" # => http://stage.example.com/edit.png
#
def image_url(source, options = {})
- url_to_asset(source, {type: :image}.merge!(options))
+ url_to_asset(source, { type: :image }.merge!(options))
end
alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route
@@ -334,7 +392,7 @@ module ActionView
# video_path("/trailers/hd.avi") # => /trailers/hd.avi
# video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi
def video_path(source, options = {})
- path_to_asset(source, {type: :video}.merge!(options))
+ path_to_asset(source, { type: :video }.merge!(options))
end
alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route
@@ -346,7 +404,7 @@ module ActionView
# video_url "hd.avi", host: "http://stage.example.com" # => http://stage.example.com/hd.avi
#
def video_url(source, options = {})
- url_to_asset(source, {type: :video}.merge!(options))
+ url_to_asset(source, { type: :video }.merge!(options))
end
alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route
@@ -360,7 +418,7 @@ module ActionView
# audio_path("/sounds/horse.wav") # => /sounds/horse.wav
# audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav
def audio_path(source, options = {})
- path_to_asset(source, {type: :audio}.merge!(options))
+ path_to_asset(source, { type: :audio }.merge!(options))
end
alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route
@@ -372,7 +430,7 @@ module ActionView
# audio_url "horse.wav", host: "http://stage.example.com" # => http://stage.example.com/horse.wav
#
def audio_url(source, options = {})
- url_to_asset(source, {type: :audio}.merge!(options))
+ url_to_asset(source, { type: :audio }.merge!(options))
end
alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route
@@ -385,7 +443,7 @@ module ActionView
# font_path("/dir/font.ttf") # => /dir/font.ttf
# font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf
def font_path(source, options = {})
- path_to_asset(source, {type: :font}.merge!(options))
+ path_to_asset(source, { type: :font }.merge!(options))
end
alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route
@@ -397,7 +455,7 @@ module ActionView
# font_url "font.ttf", host: "http://stage.example.com" # => http://stage.example.com/font.ttf
#
def font_url(source, options = {})
- url_to_asset(source, {type: :font}.merge!(options))
+ url_to_asset(source, { type: :font }.merge!(options))
end
alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route
end
diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb
index dba70e284e..09d243c46d 100644
--- a/actionview/lib/action_view/helpers/atom_feed_helper.rb
+++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb
@@ -1,4 +1,4 @@
-require 'set'
+require "set"
module ActionView
# = Action View Atom Feed Helpers
@@ -112,13 +112,13 @@ module ActionView
end
end
- feed_opts = {"xml:lang" => options[:language] || "en-US", "xmlns" => 'http://www.w3.org/2005/Atom'}
- feed_opts.merge!(options).reject!{|k,v| !k.to_s.match(/^xml/)}
+ feed_opts = { "xml:lang" => options[:language] || "en-US", "xmlns" => "http://www.w3.org/2005/Atom" }
+ feed_opts.merge!(options).reject! { |k,v| !k.to_s.match(/^xml/) }
xml.feed(feed_opts) do
xml.id(options[:id] || "tag:#{request.host},#{options[:schema_date]}:#{request.fullpath.split(".")[0]}")
- xml.link(:rel => 'alternate', :type => 'text/html', :href => options[:root_url] || (request.protocol + request.host_with_port))
- xml.link(:rel => 'self', :type => 'application/atom+xml', :href => options[:url] || request.url)
+ xml.link(rel: "alternate", type: "text/html", href: options[:root_url] || (request.protocol + request.host_with_port))
+ xml.link(rel: "self", type: "application/atom+xml", href: options[:url] || request.url)
yield AtomFeedBuilder.new(xml, self, options)
end
@@ -132,13 +132,13 @@ module ActionView
end
private
- # Delegate to xml builder, first wrapping the element in a xhtml
+ # Delegate to xml builder, first wrapping the element in an xhtml
# namespaced div element if the method and arguments indicate
# that an xhtml_block? is desired.
def method_missing(method, *arguments, &block)
if xhtml_block?(method, arguments)
@xml.__send__(method, *arguments) do
- @xml.div(:xmlns => 'http://www.w3.org/1999/xhtml') do |xhtml|
+ @xml.div(xmlns: "http://www.w3.org/1999/xhtml") do |xhtml|
block.call(xhtml)
end
end
@@ -153,7 +153,7 @@ module ActionView
def xhtml_block?(method, arguments)
if XHTML_TAG_NAMES.include?(method.to_s)
last = arguments.last
- last.is_a?(Hash) && last[:type].to_s == 'xhtml'
+ last.is_a?(Hash) && last[:type].to_s == "xhtml"
end
end
end
@@ -189,16 +189,15 @@ module ActionView
@xml.updated((options[:updated] || record.updated_at).xmlschema)
end
- type = options.fetch(:type, 'text/html')
+ type = options.fetch(:type, "text/html")
url = options.fetch(:url) { @view.polymorphic_url(record) }
- @xml.link(:rel => 'alternate', :type => type, :href => url) if url
+ @xml.link(rel: "alternate", type: type, href: url) if url
yield AtomBuilder.new(@xml)
end
end
end
-
end
end
end
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 401f398721..5258a01144 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -41,11 +41,11 @@ module ActionView
#
# ==== \Template digest
#
- # The template digest that's added to the cache key is computed by taking an md5 of the
+ # The template digest that's added to the cache key is computed by taking an MD5 of the
# contents of the entire template file. This ensures that your caches will automatically
# expire when you change the template file.
#
- # Note that the md5 is taken of the entire template file, not just what's within the
+ # Note that the MD5 is taken of the entire template file, not just what's within the
# cache do/end call. So it's possible that changing something outside of that call will
# still expire the cache.
#
@@ -69,11 +69,11 @@ module ActionView
# render 'comments/comments'
# render('comments/comments')
#
- # render "header" => render("comments/header")
+ # render "header" translates to render("comments/header")
#
- # render(@topic) => render("topics/topic")
- # render(topics) => render("topics/topic")
- # render(message.topics) => render("topics/topic")
+ # render(@topic) translates to render("topics/topic")
+ # render(topics) translates to render("topics/topic")
+ # render(message.topics) translates to render("topics/topic")
#
# It's not possible to derive all render calls like that, though.
# Here are a few examples of things that can't be derived:
@@ -118,7 +118,7 @@ module ActionView
#
# If you use a helper method, for example, inside a cached block and
# you then update that helper, you'll have to bump the cache as well.
- # It doesn't really matter how you do it, but the md5 of the template file
+ # It doesn't really matter how you do it, but the MD5 of the template file
# must change. One recommendation is to simply be explicit in a comment, like:
#
# <%# Helper Dependency Updated: May 6, 2012 at 6pm %>
@@ -126,44 +126,38 @@ module ActionView
#
# Now all you have to do is change that timestamp when the helper method changes.
#
- # === Automatic Collection Caching
+ # === Collection Caching
#
- # When rendering collections such as:
+ # When rendering a collection of objects that each use the same partial, a `cached`
+ # option can be passed.
#
- # <%= render @notifications %>
- # <%= render partial: 'notifications/notification', collection: @notifications %>
+ # For collections rendered such:
#
- # If the notifications/_notification partial starts with a cache call as:
+ # <%= render partial: 'projects/project', collection: @projects, cached: true %>
#
- # <% cache notification do %>
- # <%= notification.name %>
- # <% end %>
- #
- # The collection can then automatically use any cached renders for that
- # template by reading them at once instead of one by one.
+ # The `cached: true` will make Action View's rendering read several templates
+ # from cache at once instead of one call per template.
#
- # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for
- # more information on what cache calls make a template eligible for this
- # collection caching.
+ # Templates in the collection not already cached are written to cache.
#
- # The automatic cache multi read can be turned off like so:
+ # Works great alongside individual template fragment caching.
+ # For instance if the template the collection renders is cached like:
#
- # <%= render @notifications, cache: false %>
+ # # projects/_project.html.erb
+ # <% cache project do %>
+ # <%# ... %>
+ # <% end %>
#
- # === Explicit Collection Caching
+ # Any collection renders will find those cached templates when attempting
+ # to read multiple templates at once.
#
- # If the partial template doesn't start with a clean cache call as
- # mentioned above, you can still benefit from collection caching by
- # adding a special comment format anywhere in the template, like:
+ # If your collection cache depends on multiple sources (try to avoid this to keep things simple),
+ # you can name all these dependencies as part of a block that returns an array:
#
- # <%# Template Collection: notification %>
- # <% my_helper_that_calls_cache(some_arg, notification) do %>
- # <%= notification.name %>
- # <% end %>
+ # <%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>
#
- # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>,
- # so it's important that you type it out just so.
- # You can only declare one collection in a partial template file.
+ # This will include both records as part of the cache key and updating either of them will
+ # expire the cache.
def cache(name = {}, options = {}, &block)
if controller.respond_to?(:perform_caching) && controller.perform_caching
name_options = options.slice(:skip_digest, :virtual_path)
@@ -217,6 +211,8 @@ module ActionView
end
end
+ attr_reader :cache_hit # :nodoc:
+
private
def fragment_name_with_digest(name, virtual_path) #:nodoc:
@@ -230,9 +226,14 @@ module ActionView
end
end
- # TODO: Create an object that has caching read/write on it
def fragment_for(name = {}, options = nil, &block) #:nodoc:
- read_fragment_for(name, options) || write_fragment_for(name, options, &block)
+ if content = read_fragment_for(name, options)
+ @cache_hit = true
+ content
+ else
+ @cache_hit = false
+ write_fragment_for(name, options, &block)
+ end
end
def read_fragment_for(name, options) #:nodoc:
diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb
index df8d0affd0..719592b5c5 100644
--- a/actionview/lib/action_view/helpers/capture_helper.rb
+++ b/actionview/lib/action_view/helpers/capture_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/string/output_safety'
+require "active_support/core_ext/string/output_safety"
module ActionView
# = Action View Capture Helper
@@ -37,7 +37,7 @@ module ActionView
def capture(*args)
value = nil
buffer = with_output_buffer { value = yield(*args) }
- if string = buffer.presence || value and string.is_a?(String)
+ if (string = buffer.presence || value) && string.is_a?(String)
ERB::Util.html_escape string
end
end
diff --git a/actionview/lib/action_view/helpers/controller_helper.rb b/actionview/lib/action_view/helpers/controller_helper.rb
index 3569fba8c6..e86cdca4e4 100644
--- a/actionview/lib/action_view/helpers/controller_helper.rb
+++ b/actionview/lib/action_view/helpers/controller_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/module/attr_internal'
+require "active_support/core_ext/module/attr_internal"
module ActionView
module Helpers
@@ -8,7 +8,7 @@ module ActionView
attr_internal :controller, :request
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
- :flash, :action_name, :controller_name, :controller_path, :to => :controller
+ :flash, :action_name, :controller_name, :controller_path, to: :controller
def assign_controller(controller)
if @_controller = controller
diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb
index 5af92c4ff2..2a15d2aa5a 100644
--- a/actionview/lib/action_view/helpers/csrf_helper.rb
+++ b/actionview/lib/action_view/helpers/csrf_helper.rb
@@ -14,14 +14,14 @@ 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
+ # 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.
#
def csrf_meta_tags
if protect_against_forgery?
[
- tag('meta', :name => 'csrf-param', :content => request_forgery_protection_token),
- tag('meta', :name => 'csrf-token', :content => form_authenticity_token)
+ tag("meta", name: "csrf-param", content: request_forgery_protection_token),
+ tag("meta", name: "csrf-token", content: form_authenticity_token)
].join("\n").html_safe
end
end
diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb
index 233e613e97..ea7259a25d 100644
--- a/actionview/lib/action_view/helpers/date_helper.rb
+++ b/actionview/lib/action_view/helpers/date_helper.rb
@@ -1,9 +1,10 @@
-require 'date'
-require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/object/with_options'
+require "date"
+require "action_view/helpers/tag_helper"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/date/conversions"
+require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/object/with_options"
module ActionView
module Helpers
@@ -100,60 +101,60 @@ module ActionView
distance_in_minutes = ((to_time - from_time)/60.0).round
distance_in_seconds = (to_time - from_time).round
- I18n.with_options :locale => options[:locale], :scope => options[:scope] do |locale|
+ I18n.with_options locale: options[:locale], scope: options[:scope] do |locale|
case distance_in_minutes
- when 0..1
- return distance_in_minutes == 0 ?
- locale.t(:less_than_x_minutes, :count => 1) :
- locale.t(:x_minutes, :count => distance_in_minutes) unless options[:include_seconds]
-
- case distance_in_seconds
- when 0..4 then locale.t :less_than_x_seconds, :count => 5
- when 5..9 then locale.t :less_than_x_seconds, :count => 10
- when 10..19 then locale.t :less_than_x_seconds, :count => 20
- when 20..39 then locale.t :half_a_minute
- when 40..59 then locale.t :less_than_x_minutes, :count => 1
- else locale.t :x_minutes, :count => 1
- end
-
- when 2...45 then locale.t :x_minutes, :count => distance_in_minutes
- when 45...90 then locale.t :about_x_hours, :count => 1
+ when 0..1
+ return distance_in_minutes == 0 ?
+ locale.t(:less_than_x_minutes, count: 1) :
+ locale.t(:x_minutes, count: distance_in_minutes) unless options[:include_seconds]
+
+ case distance_in_seconds
+ when 0..4 then locale.t :less_than_x_seconds, count: 5
+ when 5..9 then locale.t :less_than_x_seconds, count: 10
+ when 10..19 then locale.t :less_than_x_seconds, count: 20
+ when 20..39 then locale.t :half_a_minute
+ when 40..59 then locale.t :less_than_x_minutes, count: 1
+ else locale.t :x_minutes, count: 1
+ end
+
+ when 2...45 then locale.t :x_minutes, count: distance_in_minutes
+ when 45...90 then locale.t :about_x_hours, count: 1
# 90 mins up to 24 hours
- when 90...1440 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round
+ when 90...1440 then locale.t :about_x_hours, count: (distance_in_minutes.to_f / 60.0).round
# 24 hours up to 42 hours
- when 1440...2520 then locale.t :x_days, :count => 1
+ when 1440...2520 then locale.t :x_days, count: 1
# 42 hours up to 30 days
- when 2520...43200 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round
+ when 2520...43200 then locale.t :x_days, count: (distance_in_minutes.to_f / 1440.0).round
# 30 days up to 60 days
- when 43200...86400 then locale.t :about_x_months, :count => (distance_in_minutes.to_f / 43200.0).round
+ 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
+ when 86400...525600 then locale.t :x_months, count: (distance_in_minutes.to_f / 43200.0).round
else
- if from_time.acts_like?(:time) && to_time.acts_like?(:time)
- fyear = from_time.year
- fyear += 1 if from_time.month >= 3
- tyear = to_time.year
- tyear -= 1 if to_time.month < 3
- leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count{|x| Date.leap?(x)}
- minute_offset_for_leap_year = leap_years * 1440
- # Discount the leap year days when calculating year distance.
- # e.g. if there are 20 leap year days between 2 dates having the same day
- # and month then the based on 365 days calculation
- # the distance in years will come out to over 80 years when in written
- # English it would read better as about 80 years.
- minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
- else
- minutes_with_offset = distance_in_minutes
- end
- remainder = (minutes_with_offset % MINUTES_IN_YEAR)
- distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
- if remainder < MINUTES_IN_QUARTER_YEAR
- locale.t(:about_x_years, :count => distance_in_years)
- elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
- locale.t(:over_x_years, :count => distance_in_years)
- else
- locale.t(:almost_x_years, :count => distance_in_years + 1)
- end
+ if from_time.acts_like?(:time) && to_time.acts_like?(:time)
+ fyear = from_time.year
+ fyear += 1 if from_time.month >= 3
+ tyear = to_time.year
+ tyear -= 1 if to_time.month < 3
+ leap_years = (fyear > tyear) ? 0 : (fyear..tyear).count { |x| Date.leap?(x) }
+ minute_offset_for_leap_year = leap_years * 1440
+ # Discount the leap year days when calculating year distance.
+ # e.g. if there are 20 leap year days between 2 dates having the same day
+ # and month then the based on 365 days calculation
+ # the distance in years will come out to over 80 years when in written
+ # English it would read better as about 80 years.
+ minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year
+ else
+ minutes_with_offset = distance_in_minutes
+ end
+ remainder = (minutes_with_offset % MINUTES_IN_YEAR)
+ distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR)
+ if remainder < MINUTES_IN_QUARTER_YEAR
+ locale.t(:about_x_years, count: distance_in_years)
+ elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR
+ locale.t(:over_x_years, count: distance_in_years)
+ else
+ locale.t(:almost_x_years, count: distance_in_years + 1)
+ end
end
end
end
@@ -226,8 +227,10 @@ module ActionView
# for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>.
# Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds)
# or the given prompt string.
- # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option
- # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags.
+ # * <tt>:with_css_classes</tt> - Set to true or a hash of strings. Use true if you want to assign generic styles for
+ # select tags. This automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second'. A hash of
+ # strings for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, <tt>:second</tt>
+ # will extend the select type with the given value. Use +html_options+ to modify every select tag in the set.
# * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
@@ -264,7 +267,7 @@ module ActionView
# date_select("article", "written_on", default: 3.days.from_now)
#
# # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute
- # # which is set in the form with todays date, regardless of the value in the Active Record object.
+ # # which is set in the form with today's date, regardless of the value in the Active Record object.
# date_select("article", "written_on", selected: Date.today)
#
# # Generates a date select that when POSTed is stored in the credit_card variable, in the bill_due attribute
@@ -300,7 +303,7 @@ module ActionView
# # the sunrise attribute.
# time_select("article", "start_time", include_seconds: true)
#
- # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30 and 45.
+ # # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
# time_select 'game', 'game_time', {minute_step: 15}
#
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
@@ -679,19 +682,19 @@ module ActionView
def time_tag(date_or_time, *args, &block)
options = args.extract_options!
format = options.delete(:format) || :long
- content = args.first || I18n.l(date_or_time, :format => format)
+ 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: datetime), &block)
end
end
class DateTimeSelector #:nodoc:
include ActionView::Helpers::TagHelper
- DEFAULT_PREFIX = 'date'.freeze
+ DEFAULT_PREFIX = "date".freeze
POSITION = {
- :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
+ year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
}.freeze
AMPM_TRANSLATION = Hash[
@@ -707,8 +710,8 @@ module ActionView
@options = options.dup
@html_options = html_options.dup
@datetime = datetime
- @options[:datetime_separator] ||= ' &mdash; '
- @options[:time_separator] ||= ' : '
+ @options[:datetime_separator] ||= " &mdash; "
+ @options[:time_separator] ||= " : "
end
def select_datetime
@@ -778,7 +781,7 @@ module ActionView
if @options[:use_hidden] || @options[:discard_minute]
build_hidden(:minute, min)
else
- build_options_and_select(:minute, min, :step => @options[:minute_step])
+ build_options_and_select(:minute, min, step: @options[:minute_step])
end
end
@@ -798,7 +801,7 @@ module ActionView
if @options[:use_hidden] || @options[:discard_day]
build_hidden(:day, day || 1)
else
- build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false, :use_two_digit_numbers => @options[:use_two_digit_numbers])
+ build_options_and_select(:day, day, start: 1, end: 31, leading_zeros: false, use_two_digit_numbers: @options[:use_two_digit_numbers])
end
end
@@ -808,7 +811,7 @@ module ActionView
else
month_options = []
1.upto(12) do |month_number|
- options = { :value => month_number }
+ options = { value: month_number }
options[:selected] = "selected" if month == month_number
month_options << content_tag("option".freeze, month_name(month_number), options) + "\n"
end
@@ -818,7 +821,7 @@ module ActionView
def select_year
if !@datetime || @datetime == 0
- val = '1'
+ val = "1"
middle_year = Date.today.year
else
val = middle_year = year
@@ -858,7 +861,7 @@ module ActionView
# valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid.
def set_day_if_discarded
if @datetime && @options[:discard_day]
- @datetime = @datetime.change(:day => 1)
+ @datetime = @datetime.change(day: 1)
end
end
@@ -883,7 +886,7 @@ module ActionView
# "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
def translated_month_names
key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
- I18n.translate(key, :locale => @options[:locale])
+ I18n.translate(key, locale: @options[:locale])
end
# Looks up month names by number (1-based):
@@ -911,11 +914,11 @@ module ActionView
if @options[:use_month_numbers]
number
elsif @options[:use_two_digit_numbers]
- '%02d' % number
+ "%02d" % number
elsif @options[:add_month_numbers]
"#{number} - #{month_names[number]}"
elsif format_string = @options[:month_format_string]
- format_string % {number: number, name: month_names[number]}
+ format_string % { number: number, name: month_names[number] }
else
month_names[number]
end
@@ -926,7 +929,7 @@ module ActionView
end
def translated_date_order
- date_order = I18n.translate(:'date.order', :locale => @options[:locale], :default => [])
+ date_order = I18n.translate(:'date.order', locale: @options[:locale], default: [])
date_order = date_order.map(&:to_sym)
forbidden_elements = date_order - [:year, :month, :day]
@@ -973,7 +976,7 @@ module ActionView
select_options = []
start.step(stop, step) do |i|
value = leading_zeros ? sprintf("%02d", i) : i
- tag_options = { :value => value }
+ tag_options = { value: value }
tag_options[:selected] = "selected" if selected == i
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
@@ -990,35 +993,51 @@ module ActionView
# </select>"
def build_select(type, select_options_as_html)
select_options = {
- :id => input_id_from_type(type),
- :name => input_name_from_type(type)
+ id: input_id_from_type(type),
+ name: input_name_from_type(type)
}.merge!(@html_options)
- select_options[:disabled] = 'disabled' if @options[:disabled]
- select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes]
+ 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 << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank]
+ 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
(content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe
end
+ # Builds the css class value for the select element
+ # css_class_attribute(:year, 'date optional', { year: 'my-year' })
+ # => "date optional my-year"
+ def css_class_attribute(type, html_options_class, options) # :nodoc:
+ css_class = \
+ case options
+ when Hash
+ options[type.to_sym]
+ else
+ type
+ end
+
+ [html_options_class, css_class].compact.join(" ")
+ end
+
# Builds a prompt option tag with supplied options or from default options.
# prompt_option_tag(:month, prompt: 'Select month')
# => "<option value="">Select month</option>"
def prompt_option_tag(type, options)
- prompt = case options
+ prompt = \
+ case options
when Hash
- default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false}
+ default_options = { year: false, month: false, day: false, hour: false, minute: false, second: false }
default_options.merge!(options)[type.to_sym]
when String
options
else
- I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale])
- end
+ I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
+ end
- prompt ? content_tag("option".freeze, prompt, :value => '') : ''
+ prompt ? content_tag("option".freeze, prompt, value: "") : ""
end
# Builds hidden input tag for date part and value.
@@ -1026,12 +1045,12 @@ module ActionView
# => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
def build_hidden(type, value)
select_options = {
- :type => "hidden",
- :id => input_id_from_type(type),
- :name => input_name_from_type(type),
- :value => value
+ type: "hidden",
+ id: input_id_from_type(type),
+ name: input_name_from_type(type),
+ value: value
}.merge!(@html_options.slice(:disabled))
- select_options[:disabled] = 'disabled' if @options[:disabled]
+ select_options[:disabled] = "disabled" if @options[:disabled]
tag(:input, select_options) + "\n".html_safe
end
@@ -1042,7 +1061,7 @@ module ActionView
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
- field_name = @options[:field_name] || type
+ field_name = @options[:field_name] || type.to_s
if @options[:include_position]
field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
end
@@ -1053,8 +1072,8 @@ module ActionView
# Returns the id attribute for the input tag.
# => "post_written_on_1i"
def input_id_from_type(type)
- id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
- id = @options[:namespace] + '_' + id if @options[:namespace]
+ id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, "_").gsub(/[\]\)]/, "")
+ id = @options[:namespace] + "_" + id if @options[:namespace]
id
end
@@ -1062,7 +1081,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 = ""
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
@@ -1076,12 +1095,12 @@ module ActionView
return "" if @options[:use_hidden]
case type
- when :year, :month, :day
- @options[:"discard_#{type}"] ? "" : @options[:date_separator]
- when :hour
- (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
- when :minute, :second
- @options[:"discard_#{type}"] ? "" : @options[:time_separator]
+ when :year, :month, :day
+ @options[:"discard_#{type}"] ? "" : @options[:date_separator]
+ when :hour
+ (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
+ when :minute, :second
+ @options[:"discard_#{type}"] ? "" : @options[:time_separator]
end
end
end
diff --git a/actionview/lib/action_view/helpers/debug_helper.rb b/actionview/lib/action_view/helpers/debug_helper.rb
index e9dccbad1c..f61ca2c9c2 100644
--- a/actionview/lib/action_view/helpers/debug_helper.rb
+++ b/actionview/lib/action_view/helpers/debug_helper.rb
@@ -4,7 +4,6 @@ module ActionView
# Provides a set of methods for making it easier to debug Rails objects.
module Helpers
module DebugHelper
-
include TagHelper
# Returns a YAML representation of +object+ wrapped with <pre> and </pre>.
@@ -25,10 +24,10 @@ module ActionView
def debug(object)
Marshal::dump(object)
object = ERB::Util.html_escape(object.to_yaml)
- content_tag(:pre, object, :class => "debug_dump")
+ content_tag(:pre, object, class: "debug_dump")
rescue # errors from Marshal or YAML
# Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
- content_tag(:code, object.inspect, :class => "debug_dump")
+ content_tag(:code, object.inspect, class: "debug_dump")
end
end
end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index c1015ffe89..124a14f1d9 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1,14 +1,14 @@
-require 'cgi'
-require 'action_view/helpers/date_helper'
-require 'action_view/helpers/tag_helper'
-require 'action_view/helpers/form_tag_helper'
-require 'action_view/helpers/active_model_helper'
-require 'action_view/model_naming'
-require 'action_view/record_identifier'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/core_ext/string/inflections'
+require "cgi"
+require "action_view/helpers/date_helper"
+require "action_view/helpers/tag_helper"
+require "action_view/helpers/form_tag_helper"
+require "action_view/helpers/active_model_helper"
+require "action_view/model_naming"
+require "action_view/record_identifier"
+require "active_support/core_ext/module/attribute_accessors"
+require "active_support/core_ext/hash/slice"
+require "active_support/core_ext/string/output_safety"
+require "active_support/core_ext/string/inflections"
module ActionView
# = Action View Form Helpers
@@ -467,10 +467,10 @@ module ActionView
)
options[:url] ||= if options.key?(:format)
- polymorphic_path(record, format: options.delete(:format))
- else
- polymorphic_path(record, {})
- end
+ polymorphic_path(record, format: options.delete(:format))
+ else
+ polymorphic_path(record, {})
+ end
end
private :apply_form_for_options!
@@ -860,24 +860,6 @@ module ActionView
#
# file_field(:attachment, :file, class: 'file_input')
# # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" />
- #
- # ==== Gotcha
- #
- # The HTML specification says that when a file field is empty, web browsers
- # do not send any value to the server. Unfortunately this introduces a
- # gotcha: if a +User+ model has an +avatar+ field, and no file is selected,
- # then the +avatar+ parameter is empty. Thus, any mass-assignment idiom like
- #
- # @user.update(params[:user])
- #
- # wouldn't update the +avatar+ field.
- #
- # To prevent this, the helper generates an auxiliary hidden field before
- # every file field. The hidden field has the same name as the file one and
- # a blank value.
- #
- # In case you don't want the helper to generate this hidden field you can
- # specify the <tt>include_hidden: false</tt> option.
def file_field(object_name, method, options = {})
Tags::FileField.new(object_name, method, self, options).render
end
@@ -983,6 +965,7 @@ module ActionView
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
#
+ # # Let's say that @user.receive_newsletter returns "no":
# radio_button("user", "receive_newsletter", "yes")
# radio_button("user", "receive_newsletter", "no")
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1066,7 +1049,7 @@ module ActionView
# Returns a text_field of type "time".
#
# The default value is generated by trying to call +strftime+ with "%T.%L"
- # on the objects's value. It is still possible to override that
+ # on the object's value. It is still possible to override that
# by passing the "value" option.
#
# === Options
@@ -1092,38 +1075,9 @@ module ActionView
Tags::TimeField.new(object_name, method, self, options).render
end
- # Returns a text_field of type "datetime".
- #
- # datetime_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
- #
- # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
- # on the object's value, which makes it behave as expected for instances
- # of DateTime and ActiveSupport::TimeWithZone.
- #
- # @user.born_on = Date.new(1984, 1, 12)
- # datetime_field("user", "born_on")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
- #
- # You can create values for the "min" and "max" attributes by passing
- # instances of Date or Time to the options hash.
- #
- # datetime_field("user", "born_on", min: Date.today)
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
- #
- # Alternatively, you can pass a String formatted as an ISO8601 datetime
- # with UTC offset as the values for "min" and "max."
- #
- # datetime_field("user", "born_on", min: "2014-05-20T00:00:00+0000")
- # # => <input id="user_born_on" name="user[born_on]" type="datetime" min="2014-05-20T00:00:00.000+0000" />
- #
- def datetime_field(object_name, method, options = {})
- Tags::DatetimeField.new(object_name, method, self, options).render
- end
-
# Returns a text_field of type "datetime-local".
#
- # datetime_local_field("user", "born_on")
+ # datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
#
# The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
@@ -1131,25 +1085,27 @@ module ActionView
# of DateTime and ActiveSupport::TimeWithZone.
#
# @user.born_on = Date.new(1984, 1, 12)
- # datetime_local_field("user", "born_on")
+ # datetime_field("user", "born_on")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
#
# You can create values for the "min" and "max" attributes by passing
# instances of Date or Time to the options hash.
#
- # datetime_local_field("user", "born_on", min: Date.today)
+ # datetime_field("user", "born_on", min: Date.today)
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
#
# Alternatively, you can pass a String formatted as an ISO8601 datetime as
# the values for "min" and "max."
#
- # datetime_local_field("user", "born_on", min: "2014-05-20T00:00:00")
+ # datetime_field("user", "born_on", min: "2014-05-20T00:00:00")
# # => <input id="user_born_on" name="user[born_on]" type="datetime-local" min="2014-05-20T00:00:00.000" />
#
- def datetime_local_field(object_name, method, options = {})
+ def datetime_field(object_name, method, options = {})
Tags::DatetimeLocalField.new(object_name, method, self, options).render
end
+ alias datetime_local_field datetime_field
+
# Returns a text_field of type "month".
#
# month_field("user", "born_on")
@@ -1315,7 +1271,7 @@ module ActionView
end
def self._to_partial_path
- @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, '')
+ @_to_partial_path ||= name.demodulize.underscore.sub!(/_builder$/, "")
end
def to_partial_path
@@ -1331,7 +1287,7 @@ module ActionView
@object_name, @object, @template, @options = object_name, object, template, options
@default_options = @options ? @options.slice(:index, :namespace) : {}
if @object_name.to_s.match(/\[\]$/)
- if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param)
+ if (object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param)
@auto_index = object.to_param
else
raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
@@ -1618,13 +1574,13 @@ module ActionView
end
record_name = if index
- "#{object_name}[#{index}][#{record_name}]"
- elsif record_name.to_s.end_with?('[]')
- record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
- "#{object_name}#{record_name}"
- else
- "#{object_name}[#{record_name}]"
- end
+ "#{object_name}[#{index}][#{record_name}]"
+ elsif record_name.to_s.end_with?("[]")
+ record_name = record_name.to_s.sub(/(.*)\[\]$/, "[\\1][#{record_object.id}]")
+ "#{object_name}#{record_name}"
+ else
+ "#{object_name}[#{record_name}]"
+ end
fields_options[:child_index] = index
@template.fields_for(record_name, record_object, fields_options, &block)
@@ -1756,7 +1712,7 @@ module ActionView
# # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" />
# # <input type="radio" id="post_category_java" name="post[category]" value="java" />
#
- # # Let's say that @user.category returns "no":
+ # # Let's say that @user.receive_newsletter returns "no":
# radio_button("receive_newsletter", "yes")
# radio_button("receive_newsletter", "no")
# # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" />
@@ -1922,8 +1878,6 @@ module ActionView
@object_name.to_s.humanize
end
- model = model.downcase
-
defaults = []
defaults << :"helpers.submit.#{object_name}.#{key}"
defaults << :"helpers.submit.#{key}"
diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb
index 430051379d..07d4310a4e 100644
--- a/actionview/lib/action_view/helpers/form_options_helper.rb
+++ b/actionview/lib/action_view/helpers/form_options_helper.rb
@@ -1,9 +1,9 @@
-require 'cgi'
-require 'erb'
-require 'action_view/helpers/form_helper'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/array/wrap'
+require "cgi"
+require "erb"
+require "action_view/helpers/form_helper"
+require "active_support/core_ext/string/output_safety"
+require "active_support/core_ext/array/extract_options"
+require "active_support/core_ext/array/wrap"
module ActionView
# = Action View Form Option Helpers
@@ -268,10 +268,11 @@ module ActionView
# for more information.)
#
# You can also supply an array of ActiveSupport::TimeZone objects
- # as +priority_zones+, so that they will be listed above the rest of the
- # (long) list. (You can use ActiveSupport::TimeZone.us_zones as a convenience
- # for obtaining a list of the US time zones, or a Regexp to select the zones
- # of your choice)
+ # as +priority_zones+ so that they will be listed above the rest of the
+ # (long) list. You can use ActiveSupport::TimeZone.us_zones for a list
+ # of US time zones, ActiveSupport::TimeZone.country_zones(country_code)
+ # for another country's time zones, or a Regexp to select the zones of
+ # your choice.
#
# Finally, this method supports a <tt>:default</tt> option, which selects
# a default ActiveSupport::TimeZone if the object's time zone is +nil+.
@@ -362,7 +363,7 @@ module ActionView
html_attributes[:disabled] ||= disabled && option_value_selected?(value, disabled)
html_attributes[:value] = value
- content_tag_string(:option, text, html_attributes)
+ tag_builder.content_tag_string(:option, text, html_attributes)
end.join("\n").html_safe
end
@@ -577,7 +578,7 @@ module ActionView
end
zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected)
- zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true)
+ zone_options.safe_concat content_tag("option".freeze, "-------------", value: "", disabled: true)
zone_options.safe_concat "\n"
zones = zones - priority_zones
@@ -650,12 +651,12 @@ module ActionView
# The HTML specification says when nothing is select on a collection of radio buttons
# web browsers do not send any value to server.
# Unfortunately this introduces a gotcha:
- # if a +User+ model has a +category_id+ field, and in the form none category is selected no +category_id+ parameter is sent. So,
- # any strong parameters idiom like
+ # if a +User+ model has a +category_id+ field and in the form no category is selected, no +category_id+ parameter is sent. So,
+ # any strong parameters idiom like:
#
# params.require(:user).permit(...)
#
- # will raise an error since no +{user: ...}+ will be present.
+ # will raise an error since no <tt>{user: ...}</tt> will be present.
#
# To prevent this the helper generates an auxiliary hidden field before
# every collection of radio buttons. The hidden field has the same name as collection radio button and blank value.
@@ -799,7 +800,7 @@ module ActionView
end
def prompt_text(prompt)
- prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', default: 'Please select')
+ prompt.kind_of?(String) ? prompt : I18n.translate("helpers.select.prompt", default: "Please select")
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 55dac74d00..7bd473507b 100644
--- a/actionview/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionview/lib/action_view/helpers/form_tag_helper.rb
@@ -1,7 +1,7 @@
-require 'cgi'
-require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/core_ext/module/attribute_accessors'
+require "cgi"
+require "action_view/helpers/tag_helper"
+require "active_support/core_ext/string/output_safety"
+require "active_support/core_ext/module/attribute_accessors"
module ActionView
# = Action View Form Tag Helpers
@@ -134,18 +134,20 @@ module ActionView
if options.include?(:include_blank)
include_blank = options.delete(:include_blank)
+ options_for_blank_options_tag = { value: "" }
if include_blank == true
- include_blank = ''
+ include_blank = ""
+ options_for_blank_options_tag[:label] = " "
end
if include_blank
- option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, include_blank, options_for_blank_options_tag).safe_concat(option_tags)
end
end
if prompt = options.delete(:prompt)
- option_tags = content_tag("option".freeze, prompt, value: '').safe_concat(option_tags)
+ option_tags = content_tag("option".freeze, prompt, value: "").safe_concat(option_tags)
end
content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys)
@@ -516,12 +518,12 @@ module ActionView
options ||= {}
end
- options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys)
+ options = { "name" => "button", "type" => "submit" }.merge!(options.stringify_keys)
if block_given?
content_tag :button, options, &block
else
- content_tag :button, content_or_options || 'Button', options
+ content_tag :button, content_or_options || "Button", options
end
end
@@ -683,7 +685,7 @@ module ActionView
text_field_tag(name, value, options.merge(type: :time))
end
- # Creates a text field of type "datetime".
+ # Creates a text field of type "datetime-local".
#
# === Options
# * <tt>:min</tt> - The minimum acceptable value.
@@ -691,19 +693,10 @@ module ActionView
# * <tt>:step</tt> - The acceptable value granularity.
# * Otherwise accepts the same options as text_field_tag.
def datetime_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.merge(type: :datetime))
+ text_field_tag(name, value, options.merge(type: "datetime-local"))
end
- # Creates a text field of type "datetime-local".
- #
- # === Options
- # * <tt>:min</tt> - The minimum acceptable value.
- # * <tt>:max</tt> - The maximum acceptable value.
- # * <tt>:step</tt> - The acceptable value granularity.
- # * Otherwise accepts the same options as text_field_tag.
- def datetime_local_field_tag(name, value = nil, options = {})
- text_field_tag(name, value, options.merge(type: 'datetime-local'))
- end
+ alias datetime_local_field_tag datetime_field_tag
# Creates a text field of type "month".
#
@@ -862,13 +855,14 @@ module ActionView
def extra_tags_for_form(html_options)
authenticity_token = html_options.delete("authenticity_token")
- method = html_options.delete("method").to_s
+ method = html_options.delete("method").to_s.downcase
- method_tag = case method
- when /^get$/i # must be case-insensitive, but can't use downcase as might be nil
+ method_tag = \
+ case method
+ when "get"
html_options["method"] = "get"
- ''
- when /^post$/i, "", nil
+ ""
+ when "post", ""
html_options["method"] = "post"
token_tag(authenticity_token, form_options: {
action: html_options["action"],
@@ -880,7 +874,7 @@ module ActionView
action: html_options["action"],
method: method
})
- end
+ end
if html_options.delete("enforce_utf8") { true }
utf8_enforcer_tag + method_tag
@@ -902,7 +896,7 @@ module ActionView
# see http://www.w3.org/TR/html4/types.html#type-name
def sanitize_to_id(name)
- name.to_s.delete(']').tr('^-a-zA-Z0-9:.', "_")
+ name.to_s.delete("]").tr("^-a-zA-Z0-9:.", "_")
end
end
end
diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb
index ed7e882c94..22e1e74ad6 100644
--- a/actionview/lib/action_view/helpers/javascript_helper.rb
+++ b/actionview/lib/action_view/helpers/javascript_helper.rb
@@ -1,11 +1,11 @@
-require 'action_view/helpers/tag_helper'
+require "action_view/helpers/tag_helper"
module ActionView
module Helpers
module JavaScriptHelper
JS_ESCAPE_MAP = {
'\\' => '\\\\',
- '</' => '<\/',
+ "</" => '<\/',
"\r\n" => '\n',
"\n" => '\n',
"\r" => '\n',
@@ -13,8 +13,8 @@ module ActionView
"'" => "\\'"
}
- JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = '&#x2028;'
- JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = '&#x2029;'
+ JS_ESCAPE_MAP["\342\200\250".force_encoding(Encoding::UTF_8).encode!] = "&#x2028;"
+ JS_ESCAPE_MAP["\342\200\251".force_encoding(Encoding::UTF_8).encode!] = "&#x2029;"
# Escapes carriage returns and single and double quotes for JavaScript segments.
#
@@ -24,10 +24,10 @@ module ActionView
# $('some_element').replaceWith('<%= j render 'some/element_template' %>');
def escape_javascript(javascript)
if javascript
- result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] }
+ result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) { |match| JS_ESCAPE_MAP[match] }
javascript.html_safe? ? result.html_safe : result
else
- ''
+ ""
end
end
diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb
index 161aa031c6..75b898c3e9 100644
--- a/actionview/lib/action_view/helpers/number_helper.rb
+++ b/actionview/lib/action_view/helpers/number_helper.rb
@@ -1,11 +1,10 @@
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/string/output_safety'
-require 'active_support/number_helper'
+require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/string/output_safety"
+require "active_support/number_helper"
module ActionView
# = Action View Number Helpers
module Helpers #:nodoc:
-
# Provides methods for converting numbers into formatted strings.
# Methods are provided for phone numbers, currency, percentage,
# precision, positional notation, file size and pretty printing.
@@ -13,7 +12,6 @@ module ActionView
# Most methods expect a +number+ argument, and will return it
# unchanged if can't be converted into a valid number.
module NumberHelper
-
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
@@ -23,7 +21,7 @@ module ActionView
end
end
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -35,6 +33,8 @@ module ActionView
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -52,6 +52,11 @@ module ActionView
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: ".")
# # => +1.123.555.1234 x 1343
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
return unless number
options = options.symbolize_keys
@@ -256,8 +261,6 @@ module ActionView
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
# * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when
# the argument is invalid.
#
@@ -388,53 +391,53 @@ module ActionView
private
- def delegate_number_helper_method(method, number, options)
- return unless number
- options = escape_unsafe_options(options.symbolize_keys)
+ def delegate_number_helper_method(method, number, options)
+ return unless number
+ options = escape_unsafe_options(options.symbolize_keys)
- wrap_with_output_safety_handling(number, options.delete(:raise)) {
- ActiveSupport::NumberHelper.public_send(method, number, options)
- }
- end
+ wrap_with_output_safety_handling(number, options.delete(:raise)) {
+ ActiveSupport::NumberHelper.public_send(method, number, options)
+ }
+ end
- def escape_unsafe_options(options)
- options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
- options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
- options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
- options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
- options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
- options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
- options
- end
+ def escape_unsafe_options(options)
+ options[:format] = ERB::Util.html_escape(options[:format]) if options[:format]
+ options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format]
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator]
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter]
+ options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe?
+ options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units]
+ options
+ end
- def escape_units(units)
- Hash[units.map do |k, v|
- [k, ERB::Util.html_escape(v)]
- end]
- end
+ def escape_units(units)
+ Hash[units.map do |k, v|
+ [k, ERB::Util.html_escape(v)]
+ end]
+ end
- def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
- valid_float = valid_float?(number)
- raise InvalidNumberError, number if raise_on_invalid && !valid_float
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
+ valid_float = valid_float?(number)
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
- formatted_number = yield
+ formatted_number = yield
- if valid_float || number.html_safe?
- formatted_number.html_safe
- else
- formatted_number
+ if valid_float || number.html_safe?
+ formatted_number.html_safe
+ else
+ formatted_number
+ end
end
- end
- def valid_float?(number)
- !parse_float(number, false).nil?
- end
+ def valid_float?(number)
+ !parse_float(number, false).nil?
+ end
- def parse_float(number, raise_error)
- Float(number)
- rescue ArgumentError, TypeError
- raise InvalidNumberError, number if raise_error
- end
+ def parse_float(number, raise_error)
+ Float(number)
+ rescue ArgumentError, TypeError
+ raise InvalidNumberError, number if raise_error
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/output_safety_helper.rb b/actionview/lib/action_view/helpers/output_safety_helper.rb
index c0fc3b820f..8e63e59fac 100644
--- a/actionview/lib/action_view/helpers/output_safety_helper.rb
+++ b/actionview/lib/action_view/helpers/output_safety_helper.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/string/output_safety'
+require "active_support/core_ext/string/output_safety"
module ActionView #:nodoc:
# = Action View Raw Output Helper
@@ -33,6 +33,36 @@ module ActionView #:nodoc:
array.flatten.map! { |i| ERB::Util.unwrapped_html_escape(i) }.join(sep).html_safe
end
+
+ # Converts the array to a comma-separated sentence where the last element is
+ # joined by the connector word. This is the html_safe-aware version of
+ # ActiveSupport's {Array#to_sentence}[http://api.rubyonrails.org/classes/Array.html#method-i-to_sentence].
+ #
+ def to_sentence(array, options = {})
+ options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
+
+ default_connectors = {
+ words_connector: ", ",
+ two_words_connector: " and ",
+ last_word_connector: ", and "
+ }
+ if defined?(I18n)
+ i18n_connectors = I18n.translate(:'support.array', locale: options[:locale], default: {})
+ default_connectors.merge!(i18n_connectors)
+ end
+ options = default_connectors.merge!(options)
+
+ case array.length
+ when 0
+ "".html_safe
+ when 1
+ ERB::Util.html_escape(array[0])
+ when 2
+ safe_join([array[0], array[1]], options[:two_words_connector])
+ else
+ safe_join([safe_join(array[0...-1], options[:words_connector]), options[:last_word_connector], array[-1]])
+ end
+ 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 c98f2d74a8..7d7f2393ff 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -27,12 +27,12 @@ module ActionView
case options
when Hash
if block_given?
- view_renderer.render_partial(self, options.merge(:partial => options[:layout]), &block)
+ view_renderer.render_partial(self, options.merge(partial: options[:layout]), &block)
else
view_renderer.render(self, options)
end
else
- view_renderer.render_partial(self, :partial => options, :locals => locals, &block)
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
end
end
diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb
index 191a881de0..3d6ff598ee 100644
--- a/actionview/lib/action_view/helpers/sanitize_helper.rb
+++ b/actionview/lib/action_view/helpers/sanitize_helper.rb
@@ -1,5 +1,5 @@
-require 'active_support/core_ext/object/try'
-require 'rails-html-sanitizer'
+require "active_support/core_ext/object/try"
+require "rails-html-sanitizer"
module ActionView
# = Action View Sanitize Helpers
@@ -120,7 +120,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.
+ # Provided strictly for compatibility and can be removed in Rails 5.1.
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 2562504896..306b71c85e 100644
--- a/actionview/lib/action_view/helpers/tag_helper.rb
+++ b/actionview/lib/action_view/helpers/tag_helper.rb
@@ -1,39 +1,207 @@
-require 'active_support/core_ext/string/output_safety'
-require 'set'
+# frozen-string-literal: true
+
+require "active_support/core_ext/string/output_safety"
+require "set"
module ActionView
# = Action View Tag Helpers
module Helpers #:nodoc:
- # Provides methods to generate HTML tags programmatically when you can't use
- # a Builder. By default, they output XHTML compliant tags.
+ # Provides methods to generate HTML tags programmatically both as a modern
+ # HTML5 compliant builder style and legacy XHTML compliant tags.
module TagHelper
extend ActiveSupport::Concern
include CaptureHelper
include OutputSafetyHelper
- BOOLEAN_ATTRIBUTES = %w(disabled readonly multiple checked autobuffer
- autoplay controls loop selected hidden scoped async
- defer reversed ismap seamless muted required
- autofocus novalidate formnovalidate open pubdate
- itemscope allowfullscreen default inert sortable
- truespeed typemustmatch).to_set
+ BOOLEAN_ATTRIBUTES = %w(allowfullscreen async autofocus autoplay checked
+ compact controls declare default defaultchecked
+ defaultmuted defaultselected defer disabled
+ enabled formnovalidate hidden indeterminate inert
+ ismap itemscope loop multiple muted nohref
+ noresize noshade novalidate nowrap open
+ pauseonexit readonly required reversed scoped
+ seamless selected sortable truespeed typemustmatch
+ visible).to_set
BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map(&:to_sym))
- TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set
+ TAG_PREFIXES = ["aria", "data", :aria, :data].to_set
- PRE_CONTENT_STRINGS = Hash.new { "".freeze }
+ PRE_CONTENT_STRINGS = Hash.new { "" }
PRE_CONTENT_STRINGS[:textarea] = "\n"
PRE_CONTENT_STRINGS["textarea"] = "\n"
+ class TagBuilder #:nodoc:
+ include CaptureHelper
+ include OutputSafetyHelper
+
+ VOID_ELEMENTS = %i(area base br col embed hr img input keygen link meta param source track wbr).to_set
+
+ def initialize(view_context)
+ @view_context = view_context
+ end
+
+ def tag_string(name, content = nil, escape_attributes: true, **options, &block)
+ content = @view_context.capture(self, &block) if block_given?
+ if VOID_ELEMENTS.include?(name) && content.nil?
+ "<#{name.to_s.dasherize}#{tag_options(options, escape_attributes)}>".html_safe
+ else
+ content_tag_string(name.to_s.dasherize, content || "", options, escape_attributes)
+ end
+ end
+
+ def content_tag_string(name, content, options, escape = true)
+ tag_options = tag_options(options, escape) if options
+ content = ERB::Util.unwrapped_html_escape(content) if escape
+ "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
+ end
+
+ def tag_options(options, escape = true)
+ return if options.blank?
+ output = "".dup
+ sep = " "
+ options.each_pair do |key, value|
+ if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
+ value.each_pair do |k, v|
+ next if v.nil?
+ output << sep
+ output << prefix_tag_option(key, k, v, escape)
+ end
+ elsif BOOLEAN_ATTRIBUTES.include?(key)
+ if value
+ output << sep
+ output << boolean_tag_option(key)
+ end
+ elsif !value.nil?
+ output << sep
+ output << tag_option(key, value, escape)
+ end
+ end
+ output unless output.empty?
+ end
- # Returns an empty HTML tag of type +name+ which by default is XHTML
+ def boolean_tag_option(key)
+ %(#{key}="#{key}")
+ end
+
+ def tag_option(key, value, escape)
+ 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
+ end
+ %(#{key}="#{value.gsub('"'.freeze, '&quot;'.freeze)}")
+ end
+
+ private
+ def prefix_tag_option(prefix, key, value, escape)
+ key = "#{prefix}-#{key.to_s.dasherize}"
+ unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
+ value = value.to_json
+ end
+ tag_option(key, value, escape)
+ end
+
+ def respond_to_missing?(*args)
+ true
+ end
+
+ def method_missing(called, *args, &block)
+ tag_string(called, *args, &block)
+ end
+ end
+
+ # Returns an HTML tag.
+ #
+ # === Building HTML tags
+ #
+ # Builds HTML5 compliant tags with a tag proxy. Every tag can be built with:
+ #
+ # tag.<tag name>(optional content, options)
+ #
+ # where tag name can be e.g. br, div, section, article, or any tag really.
+ #
+ # ==== Passing content
+ #
+ # Tags can pass content to embed within it:
+ #
+ # tag.h1 'All titles fit to print' # => <h1>All titles fit to print</h1>
+ #
+ # tag.div tag.p('Hello world!') # => <div><p>Hello world!</p></div>
+ #
+ # Content can also be captured with a block, which is useful in templates:
+ #
+ # <%= tag.p do %>
+ # The next great American novel starts here.
+ # <% end %>
+ # # => <p>The next great American novel starts here.</p>
+ #
+ # ==== Options
+ #
+ # Use symbol keyed options to add attributes to the generated tag.
+ #
+ # tag.section class: %w( kitties puppies )
+ # # => <section class="kitties puppies"></section>
+ #
+ # tag.section id: dom_id(@post)
+ # # => <section id="<generated dom id>"></section>
+ #
+ # Pass +true+ for any attributes that can render with no values, like +disabled+ and +readonly+.
+ #
+ # tag.input type: 'text', disabled: true
+ # # => <input type="text" disabled="disabled">
+ #
+ # HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
+ # pointing to a hash of sub-attributes.
+ #
+ # To play nicely with JavaScript conventions, sub-attributes are dasherized.
+ #
+ # tag.article data: { user_id: 123 }
+ # # => <article data-user-id="123"></article>
+ #
+ # Thus <tt>data-user-id</tt> can be accessed as <tt>dataset.userId</tt>.
+ #
+ # Data attribute values are encoded to JSON, with the exception of strings, symbols and
+ # BigDecimals.
+ # 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 ) }
+ # # => <div data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]"></div>
+ #
+ # The generated attributes are escaped by default. This can be disabled using
+ # +escape_attributes+.
+ #
+ # tag.img src: 'open & shut.png'
+ # # => <img src="open &amp; shut.png">
+ #
+ # tag.img src: 'open & shut.png', escape_attributes: false
+ # # => <img src="open & shut.png">
+ #
+ # The tag builder respects
+ # {HTML5 void elements}[https://www.w3.org/TR/html5/syntax.html#void-elements]
+ # if no content is passed, and omits closing tags for those elements.
+ #
+ # # A standard element:
+ # tag.div # => <div></div>
+ #
+ # # A void element:
+ # tag.br # => <br>
+ #
+ # === Legacy syntax
+ #
+ # The following format is for legacy syntax support. It will be deprecated in future versions of Rails.
+ #
+ # tag(name, options = nil, open = false, escape = true)
+ #
+ # It returns an empty HTML tag of type +name+ which by default is XHTML
# compliant. Set +open+ to true to create an open tag compatible
# with HTML 4.0 and below. Add HTML attributes by passing an attributes
# hash to +options+. Set +escape+ to false to disable attribute value
# escaping.
#
# ==== Options
+ #
# You can use symbols or strings for the attribute names.
#
# Use +true+ with boolean attributes that can render with no value, like
@@ -42,16 +210,8 @@ module ActionView
# HTML5 <tt>data-*</tt> attributes can be set with a single +data+ key
# pointing to a hash of sub-attributes.
#
- # To play nicely with JavaScript conventions sub-attributes are dasherized.
- # For example, a key +user_id+ would render as <tt>data-user-id</tt> and
- # thus accessed as <tt>dataset.userId</tt>.
- #
- # Values are encoded to JSON, with the exception of strings, symbols and
- # BigDecimals.
- # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt>
- # from 1.4.3.
- #
# ==== Examples
+ #
# tag("br")
# # => <br />
#
@@ -72,8 +232,12 @@ module ActionView
#
# tag("div", data: {name: 'Stephen', city_state: %w(Chicago IL)})
# # => <div data-name="Stephen" data-city-state="[&quot;Chicago&quot;,&quot;IL&quot;]" />
- def tag(name, options = nil, open = false, escape = true)
- "<#{name}#{tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
+ def tag(name = nil, options = nil, open = false, escape = true)
+ if name.nil?
+ tag_builder
+ else
+ "<#{name}#{tag_builder.tag_options(options, escape) if options}#{open ? ">" : " />"}".html_safe
+ end
end
# Returns an HTML block tag of type +name+ surrounding the +content+. Add
@@ -81,6 +245,7 @@ module ActionView
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +options+ as the second parameter.
# Set escape to false to disable attribute value escaping.
+ # Note: this is legacy syntax, see +tag+ method description for details.
#
# ==== Options
# The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and
@@ -104,9 +269,9 @@ module ActionView
def content_tag(name, content_or_options_with_block = nil, options = nil, escape = true, &block)
if block_given?
options = content_or_options_with_block if content_or_options_with_block.is_a?(Hash)
- content_tag_string(name, capture(&block), options, escape)
+ tag_builder.content_tag_string(name, capture(&block), options, escape)
else
- content_tag_string(name, content_or_options_with_block, options, escape)
+ tag_builder.content_tag_string(name, content_or_options_with_block, options, escape)
end
end
@@ -124,7 +289,7 @@ module ActionView
# cdata_section("hello]]>world")
# # => <![CDATA[hello]]]]><![CDATA[>world]]>
def cdata_section(content)
- splitted = content.to_s.gsub(/\]\]\>/, ']]]]><![CDATA[>')
+ splitted = content.to_s.gsub(/\]\]\>/, "]]]]><![CDATA[>")
"<![CDATA[#{splitted}]]>".html_safe
end
@@ -140,55 +305,8 @@ module ActionView
end
private
-
- def content_tag_string(name, content, options, escape = true)
- tag_options = tag_options(options, escape) if options
- content = ERB::Util.unwrapped_html_escape(content) if escape
- "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe
- end
-
- def tag_options(options, escape = true)
- return if options.blank?
- output = ""
- sep = " ".freeze
- options.each_pair do |key, value|
- if TAG_PREFIXES.include?(key) && value.is_a?(Hash)
- value.each_pair do |k, v|
- output << sep
- output << prefix_tag_option(key, k, v, escape)
- end
- elsif BOOLEAN_ATTRIBUTES.include?(key)
- if value
- output << sep
- output << boolean_tag_option(key)
- end
- elsif !value.nil?
- output << sep
- output << tag_option(key, value, escape)
- end
- end
- output unless output.empty?
- end
-
- def prefix_tag_option(prefix, key, value, escape)
- key = "#{prefix}-#{key.to_s.dasherize}"
- unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal)
- value = value.to_json
- end
- tag_option(key, value, escape)
- end
-
- def boolean_tag_option(key)
- %(#{key}="#{key}")
- end
-
- def tag_option(key, value, escape)
- if value.is_a?(Array)
- value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze)
- else
- value = escape ? ERB::Util.unwrapped_html_escape(value) : value
- end
- %(#{key}="#{value}")
+ def tag_builder
+ @tag_builder ||= TagBuilder.new(self)
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb
index d57f26ba4f..e3e3c8b109 100644
--- a/actionview/lib/action_view/helpers/tags/base.rb
+++ b/actionview/lib/action_view/helpers/tags/base.rb
@@ -24,136 +24,136 @@ module ActionView
private
- def value(object)
- object.public_send @method_name if object
- end
+ def value(object)
+ object.public_send @method_name if object
+ end
- def value_before_type_cast(object)
- unless object.nil?
- method_before_type_cast = @method_name + "_before_type_cast"
+ def value_before_type_cast(object)
+ 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)
- object.public_send(method_before_type_cast)
- else
- value(object)
+ if value_came_from_user?(object) && object.respond_to?(method_before_type_cast)
+ object.public_send(method_before_type_cast)
+ else
+ value(object)
+ end
end
end
- end
- def value_came_from_user?(object)
- method_name = "#{@method_name}_came_from_user?"
- !object.respond_to?(method_name) || object.public_send(method_name)
- end
+ def value_came_from_user?(object)
+ method_name = "#{@method_name}_came_from_user?"
+ !object.respond_to?(method_name) || object.public_send(method_name)
+ end
- def retrieve_object(object)
- if object
- object
- elsif @template_object.instance_variable_defined?("@#{@object_name}")
- @template_object.instance_variable_get("@#{@object_name}")
+ def retrieve_object(object)
+ if object
+ object
+ elsif @template_object.instance_variable_defined?("@#{@object_name}")
+ @template_object.instance_variable_get("@#{@object_name}")
+ end
+ rescue NameError
+ # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
+ nil
end
- rescue NameError
- # As @object_name may contain the nested syntax (item[subobject]) we need to fallback to nil.
- nil
- end
- def retrieve_autoindex(pre_match)
- object = self.object || @template_object.instance_variable_get("@#{pre_match}")
- if object && object.respond_to?(:to_param)
- object.to_param
- else
- raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ def retrieve_autoindex(pre_match)
+ object = self.object || @template_object.instance_variable_get("@#{pre_match}")
+ if object && object.respond_to?(:to_param)
+ object.to_param
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}"
+ end
end
- end
- def add_default_name_and_id_for_value(tag_value, options)
- if tag_value.nil?
- add_default_name_and_id(options)
- else
- specified_id = options["id"]
- add_default_name_and_id(options)
+ def add_default_name_and_id_for_value(tag_value, options)
+ if tag_value.nil?
+ add_default_name_and_id(options)
+ else
+ specified_id = options["id"]
+ add_default_name_and_id(options)
- if specified_id.blank? && options["id"].present?
- options["id"] += "_#{sanitized_value(tag_value)}"
+ if specified_id.blank? && options["id"].present?
+ options["id"] += "_#{sanitized_value(tag_value)}"
+ end
end
end
- end
- def add_default_name_and_id(options)
- index = name_and_id_index(options)
- options["name"] = options.fetch("name"){ tag_name(options["multiple"], index) }
- options["id"] = options.fetch("id"){ tag_id(index) }
- if namespace = options.delete("namespace")
- options['id'] = options['id'] ? "#{namespace}_#{options['id']}" : namespace
+ def add_default_name_and_id(options)
+ index = name_and_id_index(options)
+ options["name"] = options.fetch("name") { tag_name(options["multiple"], index) }
+ options["id"] = options.fetch("id") { tag_id(index) }
+ if namespace = options.delete("namespace")
+ options["id"] = options["id"] ? "#{namespace}_#{options['id']}" : namespace
+ end
end
- end
- def tag_name(multiple = false, index = nil)
- # a little duplication to construct less strings
- if index
- "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
- else
- "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ def tag_name(multiple = false, index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{@object_name}[#{index}][#{sanitized_method_name}]#{"[]" if multiple}"
+ else
+ "#{@object_name}[#{sanitized_method_name}]#{"[]" if multiple}"
+ end
end
- end
- def tag_id(index = nil)
- # a little duplication to construct less strings
- if index
- "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
- else
- "#{sanitized_object_name}_#{sanitized_method_name}"
+ def tag_id(index = nil)
+ # a little duplication to construct less strings
+ if index
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
+ else
+ "#{sanitized_object_name}_#{sanitized_method_name}"
+ end
end
- end
- def sanitized_object_name
- @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
+ def sanitized_object_name
+ @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
+ end
- def sanitized_method_name
- @sanitized_method_name ||= @method_name.sub(/\?$/,"")
- end
+ def sanitized_method_name
+ @sanitized_method_name ||= @method_name.sub(/\?$/,"")
+ end
- def sanitized_value(value)
- value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
- end
+ def sanitized_value(value)
+ value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase
+ end
- def select_content_tag(option_tags, options, html_options)
- html_options = html_options.stringify_keys
- add_default_name_and_id(html_options)
+ def select_content_tag(option_tags, options, html_options)
+ html_options = html_options.stringify_keys
+ add_default_name_and_id(html_options)
- if placeholder_required?(html_options)
- raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
- options[:include_blank] ||= true unless options[:prompt]
- end
+ if placeholder_required?(html_options)
+ raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false
+ options[:include_blank] ||= true unless options[:prompt]
+ end
- value = options.fetch(:selected) { value(object) }
- select = content_tag("select", add_options(option_tags, options, value), html_options)
+ value = options.fetch(:selected) { value(object) }
+ select = content_tag("select", add_options(option_tags, options, value), html_options)
- if html_options["multiple"] && options.fetch(:include_hidden, true)
- tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select
- else
- select
+ if html_options["multiple"] && options.fetch(:include_hidden, true)
+ tag("input", disabled: html_options["disabled"], name: html_options["name"], type: "hidden", value: "") + select
+ else
+ select
+ end
end
- end
- def placeholder_required?(html_options)
- # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
- html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
- end
-
- def add_options(option_tags, options, value = nil)
- if options[:include_blank]
- option_tags = content_tag_string('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags
+ def placeholder_required?(html_options)
+ # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required
+ html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1
end
- if value.blank? && options[:prompt]
- option_tags = content_tag_string('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags
+
+ def add_options(option_tags, options, value = nil)
+ if options[:include_blank]
+ 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
+ end
+ option_tags
end
- option_tags
- end
- def name_and_id_index(options)
- options.key?("index") ? options.delete("index") || "" : @auto_index
- end
+ def name_and_id_index(options)
+ options.key?("index") ? options.delete("index") || "" : @auto_index
+ end
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 6d51f2629a..02f87fc89f 100644
--- a/actionview/lib/action_view/helpers/tags/check_box.rb
+++ b/actionview/lib/action_view/helpers/tags/check_box.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/checkable'
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
@@ -38,26 +38,26 @@ module ActionView
private
- def checked?(value)
- case value
- when TrueClass, FalseClass
- value == !!@checked_value
- when NilClass
- false
- when String
- value == @checked_value
- else
- if value.respond_to?(:include?)
- value.include?(@checked_value)
+ def checked?(value)
+ case value
+ when TrueClass, FalseClass
+ value == !!@checked_value
+ when NilClass
+ false
+ when String
+ value == @checked_value
else
- value.to_i == @checked_value.to_i
+ if value.respond_to?(:include?)
+ value.include?(@checked_value)
+ else
+ value.to_i == @checked_value.to_i
+ end
end
end
- end
- def hidden_field_for_checkbox(options)
- @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
- end
+ def hidden_field_for_checkbox(options)
+ @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe
+ end
end
end
end
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 3dda47a458..2a6bf49567 100644
--- a/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_check_boxes.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/collection_helpers'
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
@@ -20,13 +20,13 @@ module ActionView
private
- def render_component(builder)
- builder.check_box + builder.label
- end
+ def render_component(builder)
+ builder.check_box + builder.label
+ end
- def hidden_field_name #:nodoc:
- "#{super}[]"
- end
+ def hidden_field_name #:nodoc:
+ "#{super}[]"
+ end
end
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 fb51460c8e..36575b2fd0 100644
--- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb
@@ -36,81 +36,81 @@ module ActionView
private
- def instantiate_builder(builder_class, item, value, text, html_options)
- builder_class.new(@template_object, @object_name, @method_name, item,
- sanitize_attribute_name(value), text, value, html_options)
- end
-
- # Generate default options for collection helpers, such as :checked and
- # :disabled.
- def default_html_options_for_collection(item, value) #:nodoc:
- html_options = @html_options.dup
-
- [:checked, :selected, :disabled, :readonly].each do |option|
- current_value = @options[option]
- next if current_value.nil?
+ def instantiate_builder(builder_class, item, value, text, html_options)
+ builder_class.new(@template_object, @object_name, @method_name, item,
+ sanitize_attribute_name(value), text, value, html_options)
+ end
- accept = if current_value.respond_to?(:call)
- current_value.call(item)
- else
- Array(current_value).map(&:to_s).include?(value.to_s)
+ # Generate default options for collection helpers, such as :checked and
+ # :disabled.
+ def default_html_options_for_collection(item, value) #:nodoc:
+ html_options = @html_options.dup
+
+ [:checked, :selected, :disabled, :readonly].each do |option|
+ current_value = @options[option]
+ next if current_value.nil?
+
+ accept = if current_value.respond_to?(:call)
+ current_value.call(item)
+ else
+ Array(current_value).map(&:to_s).include?(value.to_s)
+ end
+
+ if accept
+ html_options[option] = true
+ elsif option == :checked
+ html_options[option] = false
+ end
end
- if accept
- html_options[option] = true
- elsif option == :checked
- html_options[option] = false
- end
+ html_options[:object] = @object
+ html_options
end
- html_options[:object] = @object
- html_options
- end
+ def sanitize_attribute_name(value) #:nodoc:
+ "#{sanitized_method_name}_#{sanitized_value(value)}"
+ end
- def sanitize_attribute_name(value) #:nodoc:
- "#{sanitized_method_name}_#{sanitized_value(value)}"
- end
+ def render_collection #:nodoc:
+ @collection.map do |item|
+ value = value_for_collection(item, @value_method)
+ text = value_for_collection(item, @text_method)
+ default_html_options = default_html_options_for_collection(item, value)
+ additional_html_options = option_html_attributes(item)
- def render_collection #:nodoc:
- @collection.map do |item|
- value = value_for_collection(item, @value_method)
- text = value_for_collection(item, @text_method)
- default_html_options = default_html_options_for_collection(item, value)
- additional_html_options = option_html_attributes(item)
+ yield item, value, text, default_html_options.merge(additional_html_options)
+ end.join.html_safe
+ end
- yield item, value, text, default_html_options.merge(additional_html_options)
- end.join.html_safe
- end
+ def render_collection_for(builder_class, &block) #:nodoc:
+ options = @options.stringify_keys
+ rendered_collection = render_collection do |item, value, text, default_html_options|
+ builder = instantiate_builder(builder_class, item, value, text, default_html_options)
- def render_collection_for(builder_class, &block) #:nodoc:
- options = @options.stringify_keys
- rendered_collection = render_collection do |item, value, text, default_html_options|
- builder = instantiate_builder(builder_class, item, value, text, default_html_options)
+ if block_given?
+ @template_object.capture(builder, &block)
+ else
+ render_component(builder)
+ end
+ end
- if block_given?
- @template_object.capture(builder, &block)
+ # Prepend a hidden field to make sure something will be sent back to the
+ # server if all radio buttons are unchecked.
+ if options.fetch("include_hidden", true)
+ hidden_field + rendered_collection
else
- render_component(builder)
+ rendered_collection
end
end
- # Prepend a hidden field to make sure something will be sent back to the
- # server if all radio buttons are unchecked.
- if options.fetch('include_hidden', true)
- hidden_field + rendered_collection
- else
- rendered_collection
+ def hidden_field #:nodoc:
+ hidden_name = @html_options[:name] || hidden_field_name
+ @template_object.hidden_field_tag(hidden_name, "", id: nil)
end
- end
- def hidden_field #:nodoc:
- hidden_name = @html_options[:name] || hidden_field_name
- @template_object.hidden_field_tag(hidden_name, "", id: nil)
- end
-
- def hidden_field_name #:nodoc:
- "#{tag_name(false, @options[:index])}"
- end
+ def hidden_field_name #:nodoc:
+ "#{tag_name(false, @options[:index])}"
+ end
end
end
end
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 21aaf122f8..eed7941cd6 100644
--- a/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_radio_buttons.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/collection_helpers'
+require "action_view/helpers/tags/collection_helpers"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/collection_select.rb b/actionview/lib/action_view/helpers/tags/collection_select.rb
index 6cb2b2e0d3..4365c714eb 100644
--- a/actionview/lib/action_view/helpers/tags/collection_select.rb
+++ b/actionview/lib/action_view/helpers/tags/collection_select.rb
@@ -13,8 +13,8 @@ module ActionView
def render
option_tags_options = {
- :selected => @options.fetch(:selected) { value(@object) },
- :disabled => @options[:disabled]
+ selected: @options.fetch(:selected) { value(@object) },
+ disabled: @options[:disabled]
}
select_content_tag(
diff --git a/actionview/lib/action_view/helpers/tags/date_select.rb b/actionview/lib/action_view/helpers/tags/date_select.rb
index 0c4ac40070..006605885a 100644
--- a/actionview/lib/action_view/helpers/tags/date_select.rb
+++ b/actionview/lib/action_view/helpers/tags/date_select.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/time/calculations'
+require "active_support/core_ext/time/calculations"
module ActionView
module Helpers
@@ -22,50 +22,50 @@ module ActionView
private
- def select_type
- self.class.select_type
- end
+ def select_type
+ self.class.select_type
+ end
- def datetime_selector(options, html_options)
- datetime = options.fetch(:selected) { value(object) || default_datetime(options) }
- @auto_index ||= nil
+ def datetime_selector(options, html_options)
+ datetime = options.fetch(:selected) { value(object) || default_datetime(options) }
+ @auto_index ||= nil
- options = options.dup
- options[:field_name] = @method_name
- options[:include_position] = true
- options[:prefix] ||= @object_name
- options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
- DateTimeSelector.new(datetime, options, html_options)
- end
+ DateTimeSelector.new(datetime, options, html_options)
+ end
- def default_datetime(options)
- return if options[:include_blank] || options[:prompt]
+ def default_datetime(options)
+ return if options[:include_blank] || options[:prompt]
- case options[:default]
- when nil
- Time.current
- when Date, Time
- options[:default]
- else
- default = options[:default].dup
+ case options[:default]
+ when nil
+ Time.current
+ when Date, Time
+ options[:default]
+ else
+ default = options[:default].dup
- # Rename :minute and :second to :min and :sec
- default[:min] ||= default[:minute]
- default[:sec] ||= default[:second]
+ # Rename :minute and :second to :min and :sec
+ default[:min] ||= default[:minute]
+ default[:sec] ||= default[:second]
- time = Time.current
+ time = Time.current
- [:year, :month, :day, :hour, :min, :sec].each do |key|
- default[key] ||= time.send(key)
- end
+ [:year, :month, :day, :hour, :min, :sec].each do |key|
+ default[key] ||= time.send(key)
+ end
- Time.utc(
- default[:year], default[:month], default[:day],
- default[:hour], default[:min], default[:sec]
- )
+ Time.utc(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/datetime_field.rb b/actionview/lib/action_view/helpers/tags/datetime_field.rb
index b2cee9d198..b3940c7e44 100644
--- a/actionview/lib/action_view/helpers/tags/datetime_field.rb
+++ b/actionview/lib/action_view/helpers/tags/datetime_field.rb
@@ -14,7 +14,7 @@ module ActionView
private
def format_date(value)
- value.try(:strftime, "%Y-%m-%dT%T.%L%z")
+ raise NotImplementedError
end
def datetime_value(value)
diff --git a/actionview/lib/action_view/helpers/tags/file_field.rb b/actionview/lib/action_view/helpers/tags/file_field.rb
index e6a1d9c62d..476b820d84 100644
--- a/actionview/lib/action_view/helpers/tags/file_field.rb
+++ b/actionview/lib/action_view/helpers/tags/file_field.rb
@@ -2,21 +2,6 @@ module ActionView
module Helpers
module Tags # :nodoc:
class FileField < TextField # :nodoc:
-
- def render
- options = @options.stringify_keys
-
- if options.fetch("include_hidden", true)
- add_default_name_and_id(options)
- options[:type] = "file"
- tag("input", name: options["name"], type: "hidden", value: "") + tag("input", options)
- else
- options.delete("include_hidden")
- @options = options
-
- super
- end
- end
end
end
end
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 2ed4712dac..20e312dd0f 100644
--- a/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb
+++ b/actionview/lib/action_view/helpers/tags/grouped_collection_select.rb
@@ -15,8 +15,8 @@ module ActionView
def render
option_tags_options = {
- :selected => @options.fetch(:selected) { value(@object) },
- :disabled => @options[:disabled]
+ selected: @options.fetch(:selected) { value(@object) },
+ disabled: @options[:disabled]
}
select_content_tag(
diff --git a/actionview/lib/action_view/helpers/tags/password_field.rb b/actionview/lib/action_view/helpers/tags/password_field.rb
index 6099fa6f19..444ef65074 100644
--- a/actionview/lib/action_view/helpers/tags/password_field.rb
+++ b/actionview/lib/action_view/helpers/tags/password_field.rb
@@ -3,7 +3,7 @@ module ActionView
module Tags # :nodoc:
class PasswordField < TextField # :nodoc:
def render
- @options = {:value => nil}.merge!(@options)
+ @options = { value: nil }.merge!(@options)
super
end
end
diff --git a/actionview/lib/action_view/helpers/tags/radio_button.rb b/actionview/lib/action_view/helpers/tags/radio_button.rb
index 4849c537a5..43dbd32083 100644
--- a/actionview/lib/action_view/helpers/tags/radio_button.rb
+++ b/actionview/lib/action_view/helpers/tags/radio_button.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/checkable'
+require "action_view/helpers/tags/checkable"
module ActionView
module Helpers
@@ -22,9 +22,9 @@ module ActionView
private
- def checked?(value)
- value.to_s == @tag_value.to_s
- end
+ def checked?(value)
+ value.to_s == @tag_value.to_s
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/select.rb b/actionview/lib/action_view/helpers/tags/select.rb
index 180900cc8d..667c7e945a 100644
--- a/actionview/lib/action_view/helpers/tags/select.rb
+++ b/actionview/lib/action_view/helpers/tags/select.rb
@@ -13,8 +13,8 @@ module ActionView
def render
option_tags_options = {
- :selected => @options.fetch(:selected) { value(@object) },
- :disabled => @options[:disabled]
+ selected: @options.fetch(:selected) { value(@object) },
+ disabled: @options[:disabled]
}
option_tags = if grouped_choices?
@@ -28,13 +28,13 @@ module ActionView
private
- # Grouped choices look like this:
- #
- # [nil, []]
- # { nil => [] }
- def grouped_choices?
- !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
- end
+ # Grouped choices look like this:
+ #
+ # [nil, []]
+ # { nil => [] }
+ def grouped_choices?
+ !@choices.empty? && @choices.first.respond_to?(:last) && Array === @choices.first.last
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/text_area.rb b/actionview/lib/action_view/helpers/tags/text_area.rb
index 69038c1498..31e3a9e9b1 100644
--- a/actionview/lib/action_view/helpers/tags/text_area.rb
+++ b/actionview/lib/action_view/helpers/tags/text_area.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/placeholderable'
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
diff --git a/actionview/lib/action_view/helpers/tags/text_field.rb b/actionview/lib/action_view/helpers/tags/text_field.rb
index 5c576a20ca..4306c3543d 100644
--- a/actionview/lib/action_view/helpers/tags/text_field.rb
+++ b/actionview/lib/action_view/helpers/tags/text_field.rb
@@ -1,4 +1,4 @@
-require 'action_view/helpers/tags/placeholderable'
+require "action_view/helpers/tags/placeholderable"
module ActionView
module Helpers
@@ -23,9 +23,9 @@ module ActionView
private
- def field_type
- self.class.field_type
- end
+ def field_type
+ self.class.field_type
+ end
end
end
end
diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb
index 8b6655481d..62b1df81c6 100644
--- a/actionview/lib/action_view/helpers/tags/translator.rb
+++ b/actionview/lib/action_view/helpers/tags/translator.rb
@@ -16,24 +16,24 @@ module ActionView
protected
- attr_reader :object_name, :method_and_value, :scope, :model
+ attr_reader :object_name, :method_and_value, :scope, :model
private
- def i18n_default
- if model
- key = model.model_name.i18n_key
- ["#{key}.#{method_and_value}".to_sym, ""]
- else
- ""
+ def i18n_default
+ if model
+ key = model.model_name.i18n_key
+ ["#{key}.#{method_and_value}".to_sym, ""]
+ else
+ ""
+ end
end
- end
- def human_attribute_name
- if model && model.class.respond_to?(:human_attribute_name)
- model.class.human_attribute_name(method_and_value)
+ def human_attribute_name
+ if model && model.class.respond_to?(:human_attribute_name)
+ model.class.human_attribute_name(method_and_value)
+ end
end
- end
end
end
end
diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb
index 58ce042f12..bd3371ccc8 100644
--- a/actionview/lib/action_view/helpers/text_helper.rb
+++ b/actionview/lib/action_view/helpers/text_helper.rb
@@ -1,5 +1,5 @@
-require 'active_support/core_ext/string/filters'
-require 'active_support/core_ext/array/extract_options'
+require "active_support/core_ext/string/filters"
+require "active_support/core_ext/array/extract_options"
module ActionView
# = Action View Text Helpers
@@ -135,7 +135,7 @@ module ActionView
else
match = Array(phrases).map do |p|
Regexp === p ? p.to_s : Regexp.escape(p)
- end.join('|')
+ end.join("|")
if block_given?
text.gsub(/(#{match})(?![^<]*?>)/i) { |found| yield found }
@@ -225,14 +225,7 @@ module ActionView
#
# pluralize(2, 'Person', locale: :de)
# # => 2 Personen
- def pluralize(count, singular, deprecated_plural = nil, plural: nil, locale: I18n.locale)
- if deprecated_plural
- ActiveSupport::Deprecation.warn("Passing plural as a positional argument " \
- "is deprecated and will be removed in Rails 5.1. Use e.g. " \
- "pluralize(1, 'person', plural: 'people') instead.")
- plural ||= deprecated_plural
- end
-
+ def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale)
word = if (count == 1 || count =~ /^1(\.0+)?$/)
singular
else
@@ -269,10 +262,11 @@ module ActionView
end
# Returns +text+ transformed into HTML using simple formatting rules.
- # Two or more consecutive newlines(<tt>\n\n</tt>) are considered as a
- # paragraph and wrapped in <tt><p></tt> tags. One newline (<tt>\n</tt>) is
- # considered as a linebreak and a <tt><br /></tt> tag is appended. This
- # method does not remove the newlines from the +text+.
+ # Two or more consecutive newlines(<tt>\n\n</tt> or <tt>\r\n\r\n</tt>) are
+ # considered a paragraph and wrapped in <tt><p></tt> tags. One newline
+ # (<tt>\n</tt> or <tt>\r\n</tt>) is considered a linebreak and a
+ # <tt><br /></tt> tag is appended. This method does not remove the
+ # newlines from the +text+.
#
# You can pass any HTML attributes into <tt>html_options</tt>. These
# will be added to all created paragraphs.
@@ -357,7 +351,7 @@ module ActionView
# <% end %>
def cycle(first_value, *values)
options = values.extract_options!
- name = options.fetch(:name, 'default')
+ name = options.fetch(:name, "default")
values.unshift(*first_value)
@@ -431,17 +425,17 @@ module ActionView
private
- def next_index
- step_index(1)
- end
+ def next_index
+ step_index(1)
+ end
- def previous_index
- step_index(-1)
- end
+ def previous_index
+ step_index(-1)
+ end
- def step_index(n)
- (@index + n) % @values.size
- end
+ def step_index(n)
+ (@index + n) % @values.size
+ end
end
private
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 152e1b1211..47ed41a129 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -1,6 +1,6 @@
-require 'action_view/helpers/tag_helper'
-require 'active_support/core_ext/string/access'
-require 'i18n/exceptions'
+require "action_view/helpers/tag_helper"
+require "active_support/core_ext/string/access"
+require "i18n/exceptions"
module ActionView
# = Action View Translation Helpers
@@ -100,12 +100,12 @@ module ActionView
interpolations = options.except(:default, :scope)
if interpolations.any?
- title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(', ')
+ title << ", " << interpolations.map { |k, v| "#{k}: #{ERB::Util.html_escape(v)}" }.join(", ")
end
return title unless ActionView::Base.debug_missing_translation
- content_tag('span', keys.last.to_s.titleize, class: 'translation_missing', title: title)
+ content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
end
end
alias :t :translate
@@ -133,7 +133,7 @@ module ActionView
end
def html_safe_translation_key?(key)
- key.to_s =~ /(\b|_|\.)html$/
+ /(\b|_|\.)html$/.match?(key.to_s)
end
end
end
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 3a4561a083..36b7515173 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -1,7 +1,7 @@
-require 'action_view/helpers/javascript_helper'
-require 'active_support/core_ext/array/access'
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/string/output_safety'
+require "action_view/helpers/javascript_helper"
+require "active_support/core_ext/array/access"
+require "active_support/core_ext/hash/keys"
+require "active_support/core_ext/string/output_safety"
module ActionView
# = Action View URL Helpers
@@ -41,14 +41,14 @@ module ActionView
end
def _back_url # :nodoc:
- _filtered_referrer || 'javascript:history.back()'
+ _filtered_referrer || "javascript:history.back()"
end
protected :_back_url
def _filtered_referrer # :nodoc:
if controller.respond_to?(:request)
referrer = controller.request.env["HTTP_REFERER"]
- if referrer && URI(referrer).scheme != 'javascript'
+ if referrer && URI(referrer).scheme != "javascript"
referrer
end
end
@@ -298,42 +298,43 @@ module ActionView
html_options = html_options.stringify_keys
url = options.is_a?(String) ? options : url_for(options)
- remote = html_options.delete('remote')
- params = html_options.delete('params')
+ remote = html_options.delete("remote")
+ params = html_options.delete("params")
- method = html_options.delete('method').to_s
- method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : ''.html_safe
+ method = html_options.delete("method").to_s
+ method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".freeze.html_safe
- form_method = method == 'get' ? 'get' : 'post'
- form_options = html_options.delete('form') || {}
- form_options[:class] ||= html_options.delete('form_class') || 'button_to'
+ form_method = method == "get" ? "get" : "post"
+ form_options = html_options.delete("form") || {}
+ form_options[:class] ||= html_options.delete("form_class") || "button_to"
form_options[:method] = form_method
form_options[:action] = url
form_options[:'data-remote'] = true if remote
- request_token_tag = if form_method == 'post'
- token_tag(nil, form_options: form_options)
+ request_token_tag = if form_method == "post"
+ request_method = method.empty? ? "post" : method
+ token_tag(nil, form_options: { action: url, method: request_method })
else
- ''
+ "".freeze
end
html_options = convert_options_to_data_attributes(options, html_options)
- html_options['type'] = 'submit'
+ html_options["type"] = "submit"
button = if block_given?
- content_tag('button', html_options, &block)
+ content_tag("button", html_options, &block)
else
- html_options['value'] = name || url
- tag('input', html_options)
+ html_options["value"] = name || url
+ tag("input", html_options)
end
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
if params
- params.each do |param_name, value|
- inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param)
+ to_form_params(params).each do |param|
+ inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
end
end
- content_tag('form', inner_tags, form_options)
+ content_tag("form", inner_tags, form_options)
end
# Creates a link tag of the given +name+ using a URL created by the set of
@@ -480,7 +481,7 @@ module ActionView
option = html_options.delete(item).presence || next
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
}.compact
- extras = extras.empty? ? '' : '?' + extras.join('&')
+ extras = extras.empty? ? "".freeze : "?" + extras.join("&")
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
@@ -547,7 +548,9 @@ module ActionView
request_uri = url_string.index("?") ? request.fullpath : request.path
request_uri = URI.parser.unescape(request_uri).force_encoding(Encoding::BINARY)
- if url_string =~ /^\w+:\/\//
+ url_string.chomp!("/") if url_string.start_with?("/") && url_string != "/"
+
+ if %r{^\w+://}.match?(url_string)
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
else
url_string == request_uri
@@ -558,29 +561,29 @@ module ActionView
def convert_options_to_data_attributes(options, html_options)
if html_options
html_options = html_options.stringify_keys
- html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options)
+ html_options["data-remote"] = "true".freeze if link_to_remote_options?(options) || link_to_remote_options?(html_options)
- method = html_options.delete('method')
+ method = html_options.delete("method".freeze)
add_method_to_attributes!(html_options, method) if method
html_options
else
- link_to_remote_options?(options) ? {'data-remote' => 'true'} : {}
+ link_to_remote_options?(options) ? { "data-remote" => "true".freeze } : {}
end
end
def link_to_remote_options?(options)
if options.is_a?(Hash)
- options.delete('remote') || options.delete(:remote)
+ options.delete("remote".freeze) || options.delete(:remote)
end
end
def add_method_to_attributes!(html_options, method)
- if method && method.to_s.downcase != "get" && html_options["rel"] !~ /nofollow/
- html_options["rel"] = "#{html_options["rel"]} nofollow".lstrip
+ if method && method.to_s.downcase != "get".freeze && html_options["rel".freeze] !~ /nofollow/
+ html_options["rel".freeze] = "#{html_options["rel".freeze]} nofollow".lstrip
end
- html_options["data-method"] = method
+ html_options["data-method".freeze] = method
end
def token_tag(token=nil, form_options: {})
@@ -588,12 +591,59 @@ module ActionView
token ||= form_authenticity_token(form_options: form_options)
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
else
- ''
+ "".freeze
end
end
def method_tag(method)
- tag('input', type: 'hidden', name: '_method', value: method.to_s)
+ tag("input", type: "hidden", name: "_method", value: method.to_s)
+ end
+
+ # Returns an array of hashes each containing :name and :value keys
+ # 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'}]
+ #
+ # to_form_params(country: {name: 'Denmark'})
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ #
+ # to_form_params(countries: ['Denmark', 'Sweden']})
+ # # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
+ #
+ # An optional namespace can be passed to enclose key names:
+ #
+ # to_form_params({ name: 'Denmark' }, 'country')
+ # # => [{name: 'country[name]', value: 'Denmark'}]
+ def to_form_params(attribute, namespace = nil) # :nodoc:
+ 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
+ end
+
+ params = []
+ case attribute
+ when Hash
+ attribute.each do |key, value|
+ prefix = namespace ? "#{namespace}[#{key}]" : key
+ params.push(*to_form_params(value, prefix))
+ end
+ when Array
+ array_prefix = "#{namespace}[]"
+ attribute.each do |value|
+ params.push(*to_form_params(value, array_prefix))
+ end
+ else
+ params << { name: namespace, value: attribute.to_param }
+ end
+
+ params.sort_by { |pair| pair[:name] }
end
end
end
diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb
index a74a5e05f3..e8abfeac52 100644
--- a/actionview/lib/action_view/layouts.rb
+++ b/actionview/lib/action_view/layouts.rb
@@ -204,7 +204,7 @@ module ActionView
include ActionView::Rendering
included do
- class_attribute :_layout, :_layout_conditions, :instance_accessor => false
+ class_attribute :_layout, :_layout_conditions, instance_accessor: false
self._layout = nil
self._layout_conditions = {}
_write_layout_method
@@ -223,36 +223,39 @@ module ActionView
module LayoutConditions # :nodoc:
private
- # Determines whether the current action has a layout definition by
- # checking the action name against the :only and :except conditions
- # set by the <tt>layout</tt> method.
- #
- # ==== Returns
- # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
- def _conditional_layout?
- return unless super
-
- conditions = _layout_conditions
-
- if only = conditions[:only]
- only.include?(action_name)
- elsif except = conditions[:except]
- !except.include?(action_name)
- else
- true
+ # Determines whether the current action has a layout definition by
+ # checking the action name against the :only and :except conditions
+ # set by the <tt>layout</tt> method.
+ #
+ # ==== Returns
+ # * <tt>Boolean</tt> - True if the action has a layout definition, false otherwise.
+ def _conditional_layout?
+ return unless super
+
+ conditions = _layout_conditions
+
+ if only = conditions[:only]
+ only.include?(action_name)
+ elsif except = conditions[:except]
+ !except.include?(action_name)
+ else
+ true
+ end
end
- end
end
# Specify the layout to use for this class.
#
# If the specified layout is a:
# String:: the String is the template name
- # Symbol:: call the method specified by the symbol, which will return the template name
+ # Symbol:: call the method specified by the symbol
+ # Proc:: call the passed Proc
# false:: There is no layout
# true:: raise an ArgumentError
# nil:: Force default layout behavior with inheritance
#
+ # Return value of Proc & Symbol arguments should be String, false, true or nil
+ # with the same meaning as described above.
# ==== Parameters
# * <tt>layout</tt> - The layout to use.
#
@@ -262,7 +265,7 @@ module ActionView
def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
- conditions.each {|k, v| conditions[k] = Array(v).map(&:to_s) }
+ conditions.each { |k, v| conditions[k] = Array(v).map(&:to_s) }
self._layout_conditions = conditions
self._layout = layout
@@ -276,7 +279,7 @@ module ActionView
def _write_layout_method # :nodoc:
remove_possible_method(:_layout)
- prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
+ prefixes = /\blayouts/.match?(_implied_layout_name) ? [] : ["layouts"]
default_behavior = "lookup_context.find_all('#{_implied_layout_name}', #{prefixes.inspect}, false, [], { formats: formats }).first || super"
name_clause = if name
default_behavior
@@ -286,7 +289,8 @@ module ActionView
RUBY
end
- layout_definition = case _layout
+ layout_definition = \
+ case _layout
when String
_layout.inspect
when Symbol
@@ -313,7 +317,7 @@ module ActionView
raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil"
when nil
name_clause
- end
+ end
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def _layout(formats)
@@ -329,14 +333,14 @@ module ActionView
private
- # If no layout is supplied, look for a template named the return
- # value of this method.
- #
- # ==== Returns
- # * <tt>String</tt> - A template name
- def _implied_layout_name # :nodoc:
- controller_path
- end
+ # If no layout is supplied, look for a template named the return
+ # value of this method.
+ #
+ # ==== Returns
+ # * <tt>String</tt> - A template name
+ def _implied_layout_name # :nodoc:
+ controller_path
+ end
end
def _normalize_options(options) # :nodoc:
@@ -421,7 +425,7 @@ module ActionView
end
def _include_layout?(options)
- (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
+ (options.keys & [:body, :plain, :html, :inline, :partial]).empty? || options.key?(:layout)
end
end
end
diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb
index 9047dbdd85..c9f308c2a2 100644
--- a/actionview/lib/action_view/log_subscriber.rb
+++ b/actionview/lib/action_view/log_subscriber.rb
@@ -1,4 +1,4 @@
-require 'active_support/log_subscriber'
+require "active_support/log_subscriber"
module ActionView
# = Action View Log Subscriber
@@ -19,8 +19,33 @@ module ActionView
message << " (#{event.duration.round(1)}ms)"
end
end
- alias :render_partial :render_template
- alias :render_collection :render_template
+
+ def render_partial(event)
+ info do
+ message = " Rendered #{from_rails_root(event.payload[:identifier])}"
+ message << " within #{from_rails_root(event.payload[:layout])}" if event.payload[:layout]
+ message << " (#{event.duration.round(1)}ms)"
+ message << " #{cache_message(event.payload)}" if event.payload.key?(:cache_hit)
+ message
+ end
+ end
+
+ def render_collection(event)
+ identifier = event.payload[:identifier] || "templates"
+
+ info do
+ " Rendered collection of #{from_rails_root(identifier)}" \
+ " #{render_count(event.payload)} (#{event.duration.round(1)}ms)"
+ end
+ end
+
+ def start(name, id, payload)
+ if name == "render_template.action_view"
+ log_rendering_start(payload)
+ end
+
+ super
+ end
def logger
ActionView::Base.logger
@@ -28,7 +53,7 @@ module ActionView
protected
- EMPTY = ''
+ EMPTY = ""
def from_rails_root(string)
string = string.sub(rails_root, EMPTY)
string.sub!(VIEWS_PATTERN, EMPTY)
@@ -38,6 +63,32 @@ module ActionView
def rails_root
@root ||= "#{Rails.root}/"
end
+
+ def render_count(payload)
+ if payload[:cache_hits]
+ "[#{payload[:cache_hits]} / #{payload[:count]} cache hits]"
+ else
+ "[#{payload[:count]} times]"
+ end
+ end
+
+ def cache_message(payload)
+ if payload[:cache_hit]
+ "[cache hit]"
+ else
+ "[cache miss]"
+ end
+ end
+
+ private
+
+ def log_rendering_start(payload)
+ info do
+ message = " Rendering #{from_rails_root(payload[:identifier])}"
+ message << " within #{from_rails_root(payload[:layout])}" if payload[:layout]
+ message
+ end
+ end
end
end
diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb
index 126f289f55..9d6c762cc4 100644
--- a/actionview/lib/action_view/lookup_context.rb
+++ b/actionview/lib/action_view/lookup_context.rb
@@ -1,7 +1,7 @@
-require 'concurrent/map'
-require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/attribute_accessors'
-require 'action_view/template/resolver'
+require "concurrent/map"
+require "active_support/core_ext/module/remove_method"
+require "active_support/core_ext/module/attribute_accessors"
+require "action_view/template/resolver"
module ActionView
# = Action View Lookup Context
@@ -21,7 +21,7 @@ module ActionView
self.registered_details = []
def self.register_detail(name, &block)
- self.registered_details << name
+ registered_details << name
Accessors::DEFAULT_PROCS[name] = block
Accessors.send :define_method, :"default_#{name}", &block
@@ -55,9 +55,7 @@ module ActionView
class DetailsKey #:nodoc:
alias :eql? :equal?
- alias :object_hash :hash
- attr_reader :hash
@details_keys = Concurrent::Map.new
def self.get(details)
@@ -65,15 +63,15 @@ module ActionView
details = details.dup
details[:formats] &= Template::Types.symbols
end
- @details_keys[details] ||= new
+ @details_keys[details] ||= Concurrent::Map.new
end
def self.clear
@details_keys.clear
end
- def initialize
- @hash = object_hash
+ def self.digest_caches
+ @details_keys.values
end
end
@@ -132,6 +130,11 @@ module ActionView
end
alias :template_exists? :exists?
+ def any?(name, prefixes = [], partial = false)
+ @view_paths.exists?(*args_for_any(name, prefixes, partial))
+ end
+ alias :any_templates? :any?
+
# Adds fallbacks to the view paths. Useful in cases when you are rendering
# a :file.
def with_fallbacks
@@ -168,18 +171,44 @@ module ActionView
[user_details, details_key]
end
+ def args_for_any(name, prefixes, partial) # :nodoc:
+ name, prefixes = normalize_name(name, prefixes)
+ details, details_key = detail_args_for_any
+ [name, prefixes, partial || false, details, details_key]
+ end
+
+ def detail_args_for_any # :nodoc:
+ @detail_args_for_any ||= begin
+ details = {}
+
+ registered_details.each do |k|
+ if k == :variants
+ details[k] = :any
+ else
+ details[k] = Accessors::DEFAULT_PROCS[k].call
+ end
+ end
+
+ if @cache
+ [details, DetailsKey.get(details)]
+ else
+ [details, nil]
+ end
+ end
+ end
+
# Support legacy foo.erb names even though we now ignore .erb
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
prefixes = prefixes.presence
- parts = name.to_s.split('/'.freeze)
+ parts = name.to_s.split("/".freeze)
parts.shift if parts.first.empty?
name = parts.pop
return name, prefixes || [""] if parts.empty?
- parts = parts.join('/'.freeze)
+ parts = parts.join("/".freeze)
prefixes = prefixes ? prefixes.map { |p| "#{p}/#{parts}" } : [parts]
return name, prefixes
@@ -200,6 +229,10 @@ module ActionView
self.view_paths = view_paths
end
+ def digest_cache
+ details_key
+ end
+
def initialize_details(target, details)
registered_details.each do |k|
target[k] = details[k] || Accessors::DEFAULT_PROCS[k].call
diff --git a/actionview/lib/action_view/path_set.rb b/actionview/lib/action_view/path_set.rb
index f68d2a77ed..6688519ffd 100644
--- a/actionview/lib/action_view/path_set.rb
+++ b/actionview/lib/action_view/path_set.rb
@@ -69,30 +69,30 @@ module ActionView #:nodoc:
private
- def _find_all(path, prefixes, args, outside_app)
- prefixes = [prefixes] if String === prefixes
- prefixes.each do |prefix|
- paths.each do |resolver|
- if outside_app
- templates = resolver.find_all_anywhere(path, prefix, *args)
- else
- templates = resolver.find_all(path, prefix, *args)
+ def _find_all(path, prefixes, args, outside_app)
+ prefixes = [prefixes] if String === prefixes
+ prefixes.each do |prefix|
+ paths.each do |resolver|
+ if outside_app
+ templates = resolver.find_all_anywhere(path, prefix, *args)
+ else
+ templates = resolver.find_all(path, prefix, *args)
+ end
+ return templates unless templates.empty?
end
- return templates unless templates.empty?
end
+ []
end
- []
- end
- def typecast(paths)
- paths.map do |path|
- case path
- when Pathname, String
- OptimizedFileSystemResolver.new path.to_s
- else
- path
+ def typecast(paths)
+ paths.map do |path|
+ case path
+ when Pathname, String
+ OptimizedFileSystemResolver.new path.to_s
+ else
+ path
+ end
end
end
- end
end
end
diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb
index 59d869d92d..42795ca2c7 100644
--- a/actionview/lib/action_view/railtie.rb
+++ b/actionview/lib/action_view/railtie.rb
@@ -37,16 +37,10 @@ module ActionView
end
end
- initializer "action_view.collection_caching" do |app|
- ActiveSupport.on_load(:action_controller) do
- PartialRenderer.collection_cache = app.config.action_controller.cache_store
- end
- end
-
initializer "action_view.per_request_digest_cache" do |app|
ActiveSupport.on_load(:action_view) do
if app.config.consider_all_requests_local
- app.middleware.use ActionView::Digestor::PerRequestDigestCacheExpiry
+ app.executor.to_run ActionView::Digestor::PerExecutionDigestCacheExpiry
end
end
end
@@ -57,9 +51,13 @@ module ActionView
end
end
+ initializer "action_view.collection_caching", after: "action_controller.set_configs" do |app|
+ PartialRenderer.collection_cache = app.config.action_controller.cache_store
+ end
+
rake_tasks do |app|
unless app.config.api_only
- load "action_view/tasks/dependencies.rake"
+ load "action_view/tasks/cache_digests.rake"
end
end
end
diff --git a/actionview/lib/action_view/record_identifier.rb b/actionview/lib/action_view/record_identifier.rb
index 4a2547b0fb..b39acfa0b5 100644
--- a/actionview/lib/action_view/record_identifier.rb
+++ b/actionview/lib/action_view/record_identifier.rb
@@ -1,5 +1,5 @@
-require 'active_support/core_ext/module'
-require 'action_view/model_naming'
+require "active_support/core_ext/module"
+require "action_view/model_naming"
module ActionView
# RecordIdentifier encapsulates methods used by various ActionView helpers
@@ -57,8 +57,8 @@ module ActionView
include ModelNaming
- JOIN = '_'.freeze
- NEW = 'new'.freeze
+ JOIN = "_".freeze
+ NEW = "new".freeze
# The DOM class convention is to use the singular form of an object or class.
#
diff --git a/actionview/lib/action_view/renderer/abstract_renderer.rb b/actionview/lib/action_view/renderer/abstract_renderer.rb
index aa77a77acf..3c85be49cd 100644
--- a/actionview/lib/action_view/renderer/abstract_renderer.rb
+++ b/actionview/lib/action_view/renderer/abstract_renderer.rb
@@ -15,7 +15,7 @@ module ActionView
# that new object is called in turn. This abstracts the setup and rendering
# into a separate classes for partials and templates.
class AbstractRenderer #:nodoc:
- delegate :find_template, :find_file, :template_exists?, :with_fallbacks, :with_layout_format, :formats, :to => :@lookup_context
+ delegate :find_template, :find_file, :template_exists?, :any_templates?, :with_fallbacks, :with_layout_format, :formats, to: :@lookup_context
def initialize(lookup_context)
@lookup_context = lookup_context
@@ -27,23 +27,27 @@ module ActionView
protected
- def extract_details(options)
- @lookup_context.registered_details.each_with_object({}) do |key, details|
- value = options[key]
+ def extract_details(options)
+ @lookup_context.registered_details.each_with_object({}) do |key, details|
+ value = options[key]
- details[key] = Array(value) if value
+ details[key] = Array(value) if value
+ end
end
- end
- def instrument(name, options={})
- ActiveSupport::Notifications.instrument("render_#{name}.action_view", options){ yield }
- end
+ def instrument(name, **options)
+ options[:identifier] ||= (@template && @template.identifier) || @path
+
+ ActiveSupport::Notifications.instrument("render_#{name}.action_view", options) do |payload|
+ yield payload
+ end
+ end
- def prepend_formats(formats)
- formats = Array(formats)
- return if formats.empty? || @lookup_context.html_fallback_for_js
+ def prepend_formats(formats)
+ formats = Array(formats)
+ return if formats.empty? || @lookup_context.html_fallback_for_js
- @lookup_context.formats = formats | @lookup_context.formats
- end
+ @lookup_context.formats = formats | @lookup_context.formats
+ end
end
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index a9bd257e76..c900da3277 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -1,5 +1,5 @@
-require 'action_view/renderer/partial_renderer/collection_caching'
-require 'concurrent/map'
+require "concurrent/map"
+require "action_view/renderer/partial_renderer/collection_caching"
module ActionView
class PartialIteration
@@ -294,7 +294,7 @@ module ActionView
def render(context, options, block)
setup(context, options, block)
- identifier = (@template = find_partial) ? @template.identifier : @path
+ @template = find_partial
@lookup_context.rendered_format ||= begin
if @template && @template.formats.present?
@@ -305,252 +305,246 @@ module ActionView
end
if @collection
- instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do
- render_collection
- end
+ render_collection
else
- instrument(:partial, :identifier => identifier) do
- render_partial
- end
+ render_partial
end
end
private
- def render_collection
- return nil if @collection.blank?
+ def render_collection
+ instrument(:collection, count: @collection.size) do |payload|
+ return nil if @collection.blank?
- if @options.key?(:spacer_template)
- spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
+ if @options.key?(:spacer_template)
+ spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
+ end
+
+ cache_collection_render(payload) do
+ @template ? collection_with_template : collection_without_template
+ end.join(spacer).html_safe
+ end
end
- cache_collection_render do
- @template ? collection_with_template : collection_without_template
- end.join(spacer).html_safe
- end
+ def render_partial
+ instrument(:partial) do |payload|
+ view, locals, block = @view, @locals, @block
+ object, as = @object, @variable
- def render_partial
- view, locals, block = @view, @locals, @block
- object, as = @object, @variable
+ if !block && (layout = @options[:layout])
+ layout = find_template(layout.to_s, @template_keys)
+ end
- if !block && (layout = @options[:layout])
- layout = find_template(layout.to_s, @template_keys)
- end
+ object = locals[as] if object.nil? # Respect object when object is false
+ locals[as] = object if @has_object
- object = locals[as] if object.nil? # Respect object when object is false
- locals[as] = object if @has_object
+ content = @template.render(view, locals) do |*name|
+ view._layout_for(*name, &block)
+ end
- content = @template.render(view, locals) do |*name|
- view._layout_for(*name, &block)
+ content = layout.render(view, locals) { content } if layout
+ payload[:cache_hit] = view.cache_hit
+ content
+ end
end
- content = layout.render(view, locals){ content } if layout
- content
- end
+ # Sets up instance variables needed for rendering a partial. This method
+ # 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
+ # 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)
+ @view = context
+ @options = options
+ @block = block
+
+ @locals = options[:locals] || {}
+ @details = extract_details(options)
+
+ prepend_formats(options[:formats])
+
+ partial = options[:partial]
+
+ if String === partial
+ @has_object = options.key?(:object)
+ @object = options[:object]
+ @collection = collection_from_options
+ @path = partial
+ else
+ @has_object = true
+ @object = partial
+ @collection = collection_from_object || collection_from_options
+
+ if @collection
+ paths = @collection_data = @collection.map { |o| partial_path(o) }
+ @path = paths.uniq.one? ? paths.first : nil
+ else
+ @path = partial_path
+ end
+ end
- # Sets up instance variables needed for rendering a partial. This method
- # 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
- # 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)
- @view = context
- @options = options
- @block = block
-
- @locals = options[:locals] || {}
- @details = extract_details(options)
-
- prepend_formats(options[:formats])
-
- partial = options[:partial]
-
- if String === partial
- @has_object = options.key?(:object)
- @object = options[:object]
- @collection = collection_from_options
- @path = partial
- else
- @has_object = true
- @object = partial
- @collection = collection_from_object || collection_from_options
+ if as = options[:as]
+ raise_invalid_option_as(as) unless /\A[a-z_]\w*\z/.match?(as.to_s)
+ as = as.to_sym
+ end
- if @collection
- paths = @collection_data = @collection.map { |o| partial_path(o) }
- @path = paths.uniq.one? ? paths.first : nil
+ if @path
+ @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
+ @template_keys = retrieve_template_keys
else
- @path = partial_path
+ paths.map! { |path| retrieve_variable(path, as).unshift(path) }
end
- end
- if as = options[:as]
- raise_invalid_option_as(as) unless as.to_s =~ /\A[a-z_]\w*\z/
- as = as.to_sym
+ self
end
- if @path
- @variable, @variable_counter, @variable_iteration = retrieve_variable(@path, as)
- @template_keys = retrieve_template_keys
- else
- paths.map! { |path| retrieve_variable(path, as).unshift(path) }
+ def collection_from_options
+ if @options.key?(:collection)
+ collection = @options[:collection]
+ collection ? collection.to_a : []
+ end
end
- self
- end
-
- def collection_from_options
- if @options.key?(:collection)
- collection = @options[:collection]
- collection.respond_to?(:to_ary) ? collection.to_ary : []
+ def collection_from_object
+ @object.to_ary if @object.respond_to?(:to_ary)
end
- end
-
- def collection_from_object
- @object.to_ary if @object.respond_to?(:to_ary)
- end
-
- def find_partial
- find_template(@path, @template_keys) if @path
- end
- def find_template(path, locals)
- prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
- @lookup_context.find_template(path, prefixes, true, locals, @details)
- end
-
- def collection_with_template
- view, locals, template = @view, @locals, @template
- as, counter, iteration = @variable, @variable_counter, @variable_iteration
+ def find_partial
+ find_template(@path, @template_keys) if @path
+ end
- if layout = @options[:layout]
- layout = find_template(layout, @template_keys)
+ def find_template(path, locals)
+ prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
+ @lookup_context.find_template(path, prefixes, true, locals, @details)
end
- collection_cache_eligible = automatic_cache_eligible?
+ def collection_with_template
+ view, locals, template = @view, @locals, @template
+ as, counter, iteration = @variable, @variable_counter, @variable_iteration
- partial_iteration = PartialIteration.new(@collection.size)
- locals[iteration] = partial_iteration
+ if layout = @options[:layout]
+ layout = find_template(layout, @template_keys)
+ end
- @collection.map do |object|
- locals[as] = object
- locals[counter] = partial_iteration.index
+ partial_iteration = PartialIteration.new(@collection.size)
+ locals[iteration] = partial_iteration
- content = template.render(view, locals)
+ @collection.map do |object|
+ locals[as] = object
+ locals[counter] = partial_iteration.index
- if collection_cache_eligible
- collection_cache_rendered_partial(content, object)
+ content = template.render(view, locals)
+ content = layout.render(view, locals) { content } if layout
+ partial_iteration.iterate!
+ content
end
-
- content = layout.render(view, locals) { content } if layout
- partial_iteration.iterate!
- content
end
- end
- def collection_without_template
- view, locals, collection_data = @view, @locals, @collection_data
- cache = {}
- keys = @locals.keys
+ def collection_without_template
+ view, locals, collection_data = @view, @locals, @collection_data
+ cache = {}
+ keys = @locals.keys
- partial_iteration = PartialIteration.new(@collection.size)
+ partial_iteration = PartialIteration.new(@collection.size)
- @collection.map do |object|
- index = partial_iteration.index
- path, as, counter, iteration = collection_data[index]
+ @collection.map do |object|
+ index = partial_iteration.index
+ path, as, counter, iteration = collection_data[index]
- locals[as] = object
- locals[counter] = index
- locals[iteration] = partial_iteration
+ locals[as] = object
+ locals[counter] = index
+ locals[iteration] = partial_iteration
- template = (cache[path] ||= find_template(path, keys + [as, counter]))
- content = template.render(view, locals)
- partial_iteration.iterate!
- content
+ template = (cache[path] ||= find_template(path, keys + [as, counter]))
+ content = template.render(view, locals)
+ partial_iteration.iterate!
+ content
+ end
end
- end
- # Obtains the path to where the object's partial is located. If the object
- # responds to +to_partial_path+, then +to_partial_path+ will be called and
- # will provide the path. If the object does not respond to +to_partial_path+,
- # then an +ArgumentError+ is raised.
- #
- # If +prefix_partial_path_with_controller_namespace+ is true, then this
- # method will prefix the partial paths with a namespace.
- def partial_path(object = @object)
- object = object.to_model if object.respond_to?(:to_model)
-
- path = if object.respond_to?(:to_partial_path)
- object.to_partial_path
- else
- raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
+ # Obtains the path to where the object's partial is located. If the object
+ # responds to +to_partial_path+, then +to_partial_path+ will be called and
+ # will provide the path. If the object does not respond to +to_partial_path+,
+ # then an +ArgumentError+ is raised.
+ #
+ # If +prefix_partial_path_with_controller_namespace+ is true, then this
+ # method will prefix the partial paths with a namespace.
+ def partial_path(object = @object)
+ object = object.to_model if object.respond_to?(:to_model)
+
+ path = if object.respond_to?(:to_partial_path)
+ object.to_partial_path
+ else
+ raise ArgumentError.new("'#{object.inspect}' is not an ActiveModel-compatible object. It must implement :to_partial_path.")
+ end
+
+ if @view.prefix_partial_path_with_controller_namespace
+ prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
+ else
+ path
+ end
end
- if @view.prefix_partial_path_with_controller_namespace
- prefixed_partial_names[path] ||= merge_prefix_into_object_path(@context_prefix, path.dup)
- else
- path
+ def prefixed_partial_names
+ @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
end
- end
- def prefixed_partial_names
- @prefixed_partial_names ||= PREFIXED_PARTIAL_NAMES[@context_prefix]
- end
+ def merge_prefix_into_object_path(prefix, object_path)
+ if prefix.include?(?/) && object_path.include?(?/)
+ prefixes = []
+ prefix_array = File.dirname(prefix).split("/")
+ object_path_array = object_path.split("/")[0..-3] # skip model dir & partial
- def merge_prefix_into_object_path(prefix, object_path)
- if prefix.include?(?/) && object_path.include?(?/)
- prefixes = []
- prefix_array = File.dirname(prefix).split('/')
- object_path_array = object_path.split('/')[0..-3] # skip model dir & partial
+ prefix_array.each_with_index do |dir, index|
+ break if dir == object_path_array[index]
+ prefixes << dir
+ end
- prefix_array.each_with_index do |dir, index|
- break if dir == object_path_array[index]
- prefixes << dir
+ (prefixes << object_path).join("/")
+ else
+ object_path
end
-
- (prefixes << object_path).join("/")
- else
- object_path
end
- end
- def retrieve_template_keys
- keys = @locals.keys
- keys << @variable if @has_object || @collection
- if @collection
- keys << @variable_counter
- keys << @variable_iteration
+ def retrieve_template_keys
+ keys = @locals.keys
+ keys << @variable if @has_object || @collection
+ if @collection
+ keys << @variable_counter
+ keys << @variable_iteration
+ end
+ keys
end
- keys
- end
- def retrieve_variable(path, as)
- variable = as || begin
- base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
- raise_invalid_identifier(path) unless base =~ /\A_?(.*)(?:\.\w+)*\z/
- $1.to_sym
- end
- if @collection
- variable_counter = :"#{variable}_counter"
- variable_iteration = :"#{variable}_iteration"
+ def retrieve_variable(path, as)
+ variable = as || begin
+ base = path[-1] == "/".freeze ? "".freeze : File.basename(path)
+ raise_invalid_identifier(path) unless base =~ /\A_?(.*?)(?:\.\w+)*\z/
+ $1.to_sym
+ end
+ if @collection
+ variable_counter = :"#{variable}_counter"
+ variable_iteration = :"#{variable}_iteration"
+ end
+ [variable, variable_counter, variable_iteration]
end
- [variable, variable_counter, variable_iteration]
- end
- IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
- "make sure your partial name starts with underscore."
+ IDENTIFIER_ERROR_MESSAGE = "The partial name (%s) is not a valid Ruby identifier; " +
+ "make sure your partial name starts with underscore."
- OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
- "make sure it starts with lowercase letter, " +
- "and is followed by any combination of letters, numbers and underscores."
+ OPTION_AS_ERROR_MESSAGE = "The value (%s) of the option `as` is not a valid Ruby identifier; " +
+ "make sure it starts with lowercase letter, " +
+ "and is followed by any combination of letters, numbers and underscores."
- def raise_invalid_identifier(path)
- raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
- end
+ def raise_invalid_identifier(path)
+ raise ArgumentError.new(IDENTIFIER_ERROR_MESSAGE % (path))
+ end
- def raise_invalid_option_as(as)
- raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
- end
+ def raise_invalid_option_as(as)
+ raise ArgumentError.new(OPTION_AS_ERROR_MESSAGE % (as))
+ end
end
end
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 4860f00243..1fbe209200 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
module ActionView
module CollectionCaching # :nodoc:
extend ActiveSupport::Concern
@@ -11,37 +9,28 @@ module ActionView
end
private
- def cache_collection_render
- return yield unless cache_collection?
+ def cache_collection_render(instrumentation_payload)
+ return yield unless @options[:cached]
keyed_collection = collection_by_cache_keys
- cached_partials = collection_cache.read_multi(*keyed_collection.keys)
+ cached_partials = collection_cache.read_multi(*keyed_collection.keys)
+ instrumentation_payload[:cache_hits] = cached_partials.size
@collection = keyed_collection.reject { |key, _| cached_partials.key?(key) }.values
rendered_partials = @collection.empty? ? [] : yield
index = 0
- keyed_collection.map do |cache_key, _|
- cached_partials.fetch(cache_key) do
- rendered_partials[index].tap { index += 1 }
- end
+ fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
+ rendered_partials[index].tap { index += 1 }
end
end
- def cache_collection?
- @options.fetch(:cache, automatic_cache_eligible?)
- end
-
- def automatic_cache_eligible?
- @template && @template.eligible_for_collection_caching?(as: @options[:as])
- end
-
def callable_cache_key?
- @options[:cache].respond_to?(:call)
+ @options[:cached].respond_to?(:call)
end
def collection_by_cache_keys
- seed = callable_cache_key? ? @options[:cache] : ->(i) { i }
+ seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
@collection.each_with_object({}) do |item, hash|
hash[expanded_cache_key(seed.call(item))] = item
@@ -53,10 +42,13 @@ module ActionView
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
- def collection_cache_rendered_partial(rendered_partial, key_by)
- if callable_cache_key?
- key = expanded_cache_key(@options[:cache].call(key_by))
- collection_cache.write(key, rendered_partial, @options[:cache_options])
+ def fetch_or_cache_partial(cached_partials, order_by:)
+ order_by.map do |cache_key|
+ cached_partials.fetch(cache_key) do
+ yield.tap do |rendered_partial|
+ collection_cache.write(cache_key, rendered_partial)
+ end
+ end
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 f38e2764d0..2434250b2d 100644
--- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb
+++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb
@@ -1,4 +1,4 @@
-require 'fiber'
+require "fiber"
module ActionView
# == TODO
@@ -27,17 +27,17 @@ module ActionView
private
- # This is the same logging logic as in ShowExceptions middleware.
- # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
- def log_error(exception) #:nodoc:
- logger = ActionView::Base.logger
- return unless logger
+ # This is the same logging logic as in ShowExceptions middleware.
+ # TODO Once "exceptron" is in, refactor this piece to simply re-use exceptron.
+ def log_error(exception) #:nodoc:
+ logger = ActionView::Base.logger
+ return unless logger
- message = "\n#{exception.class} (#{exception.message}):\n"
- message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
- message << " " << exception.backtrace.join("\n ")
- logger.fatal("#{message}\n\n")
- end
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
end
# For streaming, instead of rendering a given a template, we return a Body
@@ -56,48 +56,48 @@ module ActionView
private
- def delayed_render(buffer, template, layout, view, locals)
- # Wrap the given buffer in the StreamingBuffer and pass it to the
- # underlying template handler. Now, every time something is concatenated
- # to the buffer, it is not appended to an array, but streamed straight
- # to the client.
- output = ActionView::StreamingBuffer.new(buffer)
- yielder = lambda { |*name| view._layout_for(*name) }
-
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
- fiber = Fiber.new do
- if layout
- layout.render(view, locals, output, &yielder)
- else
- # If you don't have a layout, just render the thing
- # and concatenate the final result. This is the same
- # as a layout with just <%= yield %>
- output.safe_concat view._layout_for
+ def delayed_render(buffer, template, layout, view, locals)
+ # Wrap the given buffer in the StreamingBuffer and pass it to the
+ # underlying template handler. Now, every time something is concatenated
+ # to the buffer, it is not appended to an array, but streamed straight
+ # to the client.
+ output = ActionView::StreamingBuffer.new(buffer)
+ yielder = lambda { |*name| view._layout_for(*name) }
+
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
+ fiber = Fiber.new do
+ if layout
+ layout.render(view, locals, output, &yielder)
+ else
+ # If you don't have a layout, just render the thing
+ # and concatenate the final result. This is the same
+ # as a layout with just <%= yield %>
+ output.safe_concat view._layout_for
+ end
end
- end
- # Set the view flow to support streaming. It will be aware
- # when to stop rendering the layout because it needs to search
- # something in the template and vice-versa.
- view.view_flow = StreamingFlow.new(view, fiber)
+ # Set the view flow to support streaming. It will be aware
+ # when to stop rendering the layout because it needs to search
+ # something in the template and vice-versa.
+ view.view_flow = StreamingFlow.new(view, fiber)
- # Yo! Start the fiber!
- fiber.resume
+ # Yo! Start the fiber!
+ fiber.resume
- # If the fiber is still alive, it means we need something
- # from the template, so start rendering it. If not, it means
- # the layout exited without requiring anything from the template.
- if fiber.alive?
- content = template.render(view, locals, &yielder)
+ # If the fiber is still alive, it means we need something
+ # from the template, so start rendering it. If not, it means
+ # the layout exited without requiring anything from the template.
+ if fiber.alive?
+ content = template.render(view, locals, &yielder)
- # Once rendering the template is done, sets its content in the :layout key.
- view.view_flow.set(:layout, content)
+ # Once rendering the template is done, sets its content in the :layout key.
+ view.view_flow.set(:layout, content)
- # In case the layout continues yielding, we need to resume
- # the fiber until all yields are handled.
- fiber.resume while fiber.alive?
+ # In case the layout continues yielding, we need to resume
+ # the fiber until all yields are handled.
+ fiber.resume while fiber.alive?
+ end
end
end
- end
end
end
diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb
index 9d15bbfca7..f40bf8f6e2 100644
--- a/actionview/lib/action_view/renderer/template_renderer.rb
+++ b/actionview/lib/action_view/renderer/template_renderer.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/object/try'
+require "active_support/core_ext/object/try"
module ActionView
class TemplateRenderer < AbstractRenderer #:nodoc:
@@ -16,87 +16,85 @@ module ActionView
private
- # Determine the template to be rendered using the given options.
- def determine_template(options)
- keys = options.has_key?(:locals) ? options[:locals].keys : []
+ # Determine the template to be rendered using the given options.
+ def determine_template(options)
+ keys = options.has_key?(:locals) ? options[:locals].keys : []
- if options.key?(:body)
- Template::Text.new(options[:body])
- elsif options.key?(:text)
- Template::Text.new(options[:text], formats.first)
- elsif options.key?(:plain)
- Template::Text.new(options[:plain])
- elsif options.key?(:html)
- Template::HTML.new(options[:html], formats.first)
- elsif options.key?(:file)
- with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
- elsif options.key?(:inline)
- handler = Template.handler_for_extension(options[:type] || "erb")
- Template.new(options[:inline], "inline template", handler, :locals => keys)
- elsif options.key?(:template)
- if options[:template].respond_to?(:render)
- options[:template]
+ if options.key?(:body)
+ Template::Text.new(options[:body])
+ elsif options.key?(:plain)
+ Template::Text.new(options[:plain])
+ elsif options.key?(:html)
+ Template::HTML.new(options[:html], formats.first)
+ elsif options.key?(:file)
+ with_fallbacks { find_file(options[:file], nil, false, keys, @details) }
+ elsif options.key?(:inline)
+ handler = Template.handler_for_extension(options[:type] || "erb")
+ Template.new(options[:inline], "inline template", handler, locals: keys)
+ elsif options.key?(:template)
+ if options[:template].respond_to?(:render)
+ options[:template]
+ else
+ find_template(options[:template], options[:prefixes], false, keys, @details)
+ end
else
- find_template(options[:template], options[:prefixes], false, keys, @details)
+ raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html or :body option."
end
- else
- raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :html, :text or :body option."
end
- end
- # Renders the given template. A string representing the layout can be
- # supplied as well.
- def render_template(template, layout_name = nil, locals = nil) #:nodoc:
- view, locals = @view, locals || {}
+ # Renders the given template. A string representing the layout can be
+ # supplied as well.
+ def render_template(template, layout_name = nil, locals = nil) #:nodoc:
+ view, locals = @view, locals || {}
- render_with_layout(layout_name, locals) do |layout|
- instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
- template.render(view, locals) { |*name| view._layout_for(*name) }
+ render_with_layout(layout_name, locals) do |layout|
+ instrument(:template, identifier: template.identifier, layout: layout.try(:virtual_path)) do
+ template.render(view, locals) { |*name| view._layout_for(*name) }
+ end
end
end
- end
- def render_with_layout(path, locals) #:nodoc:
- layout = path && find_layout(path, locals.keys, [formats.first])
- content = yield(layout)
+ def render_with_layout(path, locals) #:nodoc:
+ layout = path && find_layout(path, locals.keys, [formats.first])
+ content = yield(layout)
- if layout
- view = @view
- view.view_flow.set(:layout, content)
- layout.render(view, locals){ |*name| view._layout_for(*name) }
- else
- content
+ if layout
+ view = @view
+ view.view_flow.set(:layout, content)
+ layout.render(view, locals) { |*name| view._layout_for(*name) }
+ else
+ content
+ end
end
- end
- # This is the method which actually finds the layout using details in the lookup
- # context object. If no layout is found, it checks if at least a layout with
- # the given name exists across all details before raising the error.
- def find_layout(layout, keys, formats)
- resolve_layout(layout, keys, formats)
- end
+ # This is the method which actually finds the layout using details in the lookup
+ # context object. If no layout is found, it checks if at least a layout with
+ # the given name exists across all details before raising the error.
+ def find_layout(layout, keys, formats)
+ resolve_layout(layout, keys, formats)
+ end
- def resolve_layout(layout, keys, formats)
- details = @details.dup
- details[:formats] = formats
+ def resolve_layout(layout, keys, formats)
+ details = @details.dup
+ details[:formats] = formats
- case layout
- when String
- begin
- if layout =~ /^\//
- with_fallbacks { find_template(layout, nil, false, keys, details) }
- else
- find_template(layout, nil, false, keys, details)
+ case layout
+ when String
+ begin
+ if layout.start_with?("/")
+ with_fallbacks { find_template(layout, nil, false, [], details) }
+ else
+ find_template(layout, nil, false, [], details)
+ end
+ rescue ActionView::MissingTemplate
+ all_details = @details.merge(formats: @lookup_context.default_formats)
+ raise unless template_exists?(layout, nil, false, [], all_details)
end
- rescue ActionView::MissingTemplate
- all_details = @details.merge(:formats => @lookup_context.default_formats)
- raise unless template_exists?(layout, nil, false, keys, all_details)
+ when Proc
+ resolve_layout(layout.call(formats), keys, formats)
+ else
+ layout
end
- when Proc
- resolve_layout(layout.call(formats), keys, formats)
- else
- layout
end
- end
end
end
diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb
index 45e78d1ad9..669cffab1a 100644
--- a/actionview/lib/action_view/routing_url_for.rb
+++ b/actionview/lib/action_view/routing_url_for.rb
@@ -1,8 +1,7 @@
-require 'action_dispatch/routing/polymorphic_routes'
+require "action_dispatch/routing/polymorphic_routes"
module ActionView
module RoutingUrlFor
-
# Returns the URL for the set of +options+ provided. This takes the
# same options as +url_for+ in Action Controller (see the
# documentation for <tt>ActionController::Base#url_for</tt>). Note that by default
diff --git a/actionview/lib/action_view/tasks/cache_digests.rake b/actionview/lib/action_view/tasks/cache_digests.rake
new file mode 100644
index 0000000000..d30b3f7797
--- /dev/null
+++ b/actionview/lib/action_view/tasks/cache_digests.rake
@@ -0,0 +1,23 @@
+namespace :cache_digests do
+ desc "Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
+ task nested_dependencies: :environment do
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:to_dep_map)
+ end
+
+ desc "Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)"
+ task dependencies: :environment do
+ abort "You must provide TEMPLATE for the task to run" unless ENV["TEMPLATE"].present?
+ puts JSON.pretty_generate ActionView::Digestor.tree(CacheDigests.template_name, CacheDigests.finder).children.map(&:name)
+ end
+
+ class CacheDigests
+ def self.template_name
+ ENV["TEMPLATE"].split(".", 2).first
+ end
+
+ def self.finder
+ ApplicationController.new.lookup_context
+ end
+ end
+end
diff --git a/actionview/lib/action_view/tasks/dependencies.rake b/actionview/lib/action_view/tasks/dependencies.rake
deleted file mode 100644
index 9932ff8b6d..0000000000
--- a/actionview/lib/action_view/tasks/dependencies.rake
+++ /dev/null
@@ -1,23 +0,0 @@
-namespace :cache_digests do
- desc 'Lookup nested dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
- task :nested_dependencies => :environment do
- abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).nested_dependencies
- end
-
- desc 'Lookup first-level dependencies for TEMPLATE (like messages/show or comments/_comment.html)'
- task :dependencies => :environment do
- abort 'You must provide TEMPLATE for the task to run' unless ENV['TEMPLATE'].present?
- puts JSON.pretty_generate ActionView::Digestor.new(CacheDigests.template_name, CacheDigests.finder).dependencies
- end
-
- class CacheDigests
- def self.template_name
- ENV['TEMPLATE'].split('.', 2).first
- end
-
- def self.finder
- ApplicationController.new.lookup_context
- end
- end
-end
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index 15fc2b71a3..98571cf55e 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -1,6 +1,6 @@
-require 'active_support/core_ext/object/try'
-require 'active_support/core_ext/kernel/singleton_class'
-require 'thread'
+require "active_support/core_ext/object/try"
+require "active_support/core_ext/kernel/singleton_class"
+require "thread"
module ActionView
# = Action View Template
@@ -65,8 +65,7 @@ module ActionView
# If you want to provide an alternate mechanism for
# specifying encodings (like ERB does via <%# encoding: ... %>),
# you may indicate that you will handle encodings yourself
- # by implementing <tt>self.handles_encoding?</tt>
- # on your handler.
+ # by implementing <tt>handles_encoding?</tt> on your handler.
#
# If you do, Rails will not try to encode the String
# into the default_internal, passing you the unaltered
@@ -130,7 +129,6 @@ module ActionView
@source = source
@identifier = identifier
@handler = handler
- @cache_name = extract_resource_cache_name
@compiled = false
@original_encoding = nil
@locals = details[:locals] || []
@@ -154,7 +152,7 @@ module ActionView
# we use a bang in this instrumentation because you don't want to
# consume this in production. This is only slow if it's being listened to.
def render(view, locals, buffer=nil, &block)
- instrument("!render_template".freeze) do
+ instrument_render_template do
compile!(view)
view.send(method_name, locals, buffer, &block)
end
@@ -166,10 +164,6 @@ module ActionView
@type ||= Types[@formats.first] if @formats.first
end
- def eligible_for_collection_caching?(as: nil)
- @cache_name == (as || inferred_cache_name).to_s
- end
-
# Receives a view object and return a template similar to self by using @virtual_path.
#
# This method is useful if you have a template object but it does not contain its source
@@ -185,12 +179,12 @@ module ActionView
name = pieces.pop
partial = !!name.sub!(/^_/, "")
lookup.disable_cache do
- lookup.find_template(name, [ pieces.join('/') ], partial, @locals)
+ lookup.find_template(name, [ pieces.join("/") ], partial, @locals)
end
end
def inspect
- @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", ''.freeze) : identifier
+ @inspect ||= defined?(Rails.root) ? identifier.sub("#{Rails.root}/", "".freeze) : identifier
end
# This method is responsible for properly setting the encoding of the
@@ -209,7 +203,7 @@ module ActionView
# Look for # encoding: *. If we find one, we'll encode the
# String in that encoding, otherwise, we'll use the
# default external encoding.
- if source.sub!(/\A#{ENCODING_FLAG}/, '')
+ if source.sub!(/\A#{ENCODING_FLAG}/, "")
encoding = magic_encoding = $1
else
encoding = Encoding.default_external
@@ -330,48 +324,39 @@ module ActionView
end
def locals_code #:nodoc:
+ # Only locals with valid variable names get set directly. Others will
+ # still be available in local_assigns.
+ locals = @locals.to_set - Module::DELEGATION_RESERVED_METHOD_NAMES
+ 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}];" }
+ locals.each_with_object("") { |key, code| code << "#{key} = #{key} = local_assigns[:#{key}];" }
end
def method_name #:nodoc:
@method_name ||= begin
m = "_#{identifier_method_name}__#{@identifier.hash}_#{__id__}"
- m.tr!('-'.freeze, '_'.freeze)
+ m.tr!("-".freeze, "_".freeze)
m
end
end
def identifier_method_name #:nodoc:
- inspect.tr('^a-z_'.freeze, '_'.freeze)
+ inspect.tr("^a-z_".freeze, "_".freeze)
end
def instrument(action, &block)
- payload = { virtual_path: @virtual_path, identifier: @identifier }
- case action
- when "!render_template".freeze
- ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, payload, &block)
- else
- ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, payload, &block)
- end
+ ActiveSupport::Notifications.instrument("#{action}.action_view".freeze, instrument_payload, &block)
end
- EXPLICIT_COLLECTION = /# Template Collection: (?<resource_name>\w+)/
+ private
- def extract_resource_cache_name
- if match = @source.match(EXPLICIT_COLLECTION) || resource_cache_call_match
- match[:resource_name]
- end
- end
-
- def resource_cache_call_match
- if @handler.respond_to?(:resource_cache_call_pattern)
- @source.match(@handler.resource_cache_call_pattern)
- end
+ def instrument_render_template(&block)
+ ActiveSupport::Notifications.instrument("!render_template.action_view".freeze, instrument_payload, &block)
end
- def inferred_cache_name
- @inferred_cache_name ||= @virtual_path.split('/'.freeze).last.sub('_'.freeze, ''.freeze)
+ def instrument_payload
+ { virtual_path: @virtual_path, identifier: @identifier }
end
end
end
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index ccee785d3e..cc90477190 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -35,10 +35,10 @@ module ActionView
prefixes = Array(prefixes)
template_type = if partial
"partial"
- elsif path =~ /layouts/i
- 'layout'
+ elsif /layouts/i.match?(path)
+ "layout"
else
- 'template'
+ "template"
end
if partial && path.present?
@@ -62,23 +62,13 @@ module ActionView
# Override to prevent #cause resetting during re-raise.
attr_reader :cause
- def initialize(template, original_exception = nil)
- if original_exception
- ActiveSupport::Deprecation.warn("Passing #original_exception is deprecated and has no effect. " \
- "Exceptions will automatically capture the original exception.", caller)
- end
-
+ def initialize(template)
super($!.message)
set_backtrace($!.backtrace)
@cause = $!
@template, @sub_templates = template, nil
end
- def original_exception
- ActiveSupport::Deprecation.warn("#original_exception is deprecated. Use #cause instead.", caller)
- cause
- end
-
def file_name
@template.identifier
end
@@ -130,18 +120,18 @@ module ActionView
if line_number
"on line ##{line_number} of "
else
- 'in '
+ "in "
end + file_name
end
def formatted_code_for(source_code, line_counter, indent, output)
- start_value = (output == :html) ? {} : ""
+ start_value = (output == :html) ? {} : []
source_code.inject(start_value) do |result, line|
line_counter += 1
if output == :html
result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line])
else
- result << "%#{indent}s: %s\n" % [line_counter, line]
+ result << "%#{indent}s: %s" % [line_counter, line]
end
end
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index ad4c353608..f4301f6f07 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -2,10 +2,10 @@ module ActionView #:nodoc:
# = Action View Template Handlers
class Template
module Handlers #:nodoc:
- autoload :Raw, 'action_view/template/handlers/raw'
- autoload :ERB, 'action_view/template/handlers/erb'
- autoload :Html, 'action_view/template/handlers/html'
- autoload :Builder, 'action_view/template/handlers/builder'
+ autoload :Raw, "action_view/template/handlers/raw"
+ autoload :ERB, "action_view/template/handlers/erb"
+ autoload :Html, "action_view/template/handlers/html"
+ autoload :Builder, "action_view/template/handlers/builder"
def self.extended(base)
base.register_default_template_handler :raw, Raw.new
diff --git a/actionview/lib/action_view/template/handlers/builder.rb b/actionview/lib/action_view/template/handlers/builder.rb
index d90b0c6378..e08a5b5db8 100644
--- a/actionview/lib/action_view/template/handlers/builder.rb
+++ b/actionview/lib/action_view/template/handlers/builder.rb
@@ -15,12 +15,12 @@ module ActionView
protected
- def require_engine
- @required ||= begin
- require "builder"
- true
+ def require_engine
+ @required ||= begin
+ require "builder"
+ true
+ end
end
- end
end
end
end
diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb
index 1f8459c24b..5d047a6991 100644
--- a/actionview/lib/action_view/template/handlers/erb.rb
+++ b/actionview/lib/action_view/template/handlers/erb.rb
@@ -1,4 +1,4 @@
-require 'erubis'
+require "erubis"
module ActionView
class Template
@@ -28,7 +28,7 @@ module ActionView
# We override to always treat <%== as escaped.
def add_expr(src, code, indicator)
case indicator
- when '=='
+ when "=="
add_expr_escaped(src, code)
else
super
@@ -39,16 +39,16 @@ module ActionView
def add_expr_literal(src, code)
flush_newline_if_pending(src)
- if code =~ BLOCK_EXPR
- src << '@output_buffer.append= ' << code
+ if BLOCK_EXPR.match?(code)
+ src << "@output_buffer.append= " << code
else
- src << '@output_buffer.append=(' << code << ');'
+ src << "@output_buffer.append=(" << code << ");"
end
end
def add_expr_escaped(src, code)
flush_newline_if_pending(src)
- if code =~ BLOCK_EXPR
+ if BLOCK_EXPR.match?(code)
src << "@output_buffer.safe_expr_append= " << code
else
src << "@output_buffer.safe_expr_append=(" << code << ");"
@@ -62,7 +62,7 @@ module ActionView
def add_postamble(src)
flush_newline_if_pending(src)
- src << '@output_buffer.to_s'
+ src << "@output_buffer.to_s"
end
def flush_newline_if_pending(src)
@@ -77,7 +77,7 @@ module ActionView
# Specify trim mode for the ERB compiler. Defaults to '-'.
# See ERB documentation for suitable values.
class_attribute :erb_trim_mode
- self.erb_trim_mode = '-'
+ self.erb_trim_mode = "-"
# Default implementation used.
class_attribute :erb_implementation
@@ -108,7 +108,7 @@ module ActionView
# expression
template_source = template.source.dup.force_encoding(Encoding::ASCII_8BIT)
- erb = template_source.gsub(ENCODING_TAG, '')
+ erb = template_source.gsub(ENCODING_TAG, "")
encoding = $2
erb.force_encoding valid_encoding(template.source.dup, encoding)
@@ -118,36 +118,11 @@ module ActionView
self.class.erb_implementation.new(
erb,
- :escape => (self.class.escape_whitelist.include? template.type),
- :trim => (self.class.erb_trim_mode == "-")
+ escape: (self.class.escape_whitelist.include? template.type),
+ trim: (self.class.erb_trim_mode == "-")
).src
end
- # Returns Regexp to extract a cached resource's name from a cache call at the
- # first line of a template.
- # The extracted cache name is captured as :resource_name.
- #
- # <% cache notification do %> # => notification
- #
- # The pattern should support templates with a beginning comment:
- #
- # <%# Still extractable even though there's a comment %>
- # <% cache notification do %> # => notification
- #
- # But fail to extract a name if a resource association is cached.
- #
- # <% cache notification.event do %> # => nil
- def resource_cache_call_pattern
- /\A
- (?:<%\#.*%>)* # optional initial comment
- \s* # followed by optional spaces or newlines
- <%\s*cache[\(\s] # followed by an ERB call to cache
- \s* # followed by optional spaces or newlines
- (?<resource_name>\w+) # capture the cache call argument as :resource_name
- [\s\)] # followed by a space or close paren
- /xm
- end
-
private
def valid_encoding(string, encoding)
diff --git a/actionview/lib/action_view/template/handlers/raw.rb b/actionview/lib/action_view/template/handlers/raw.rb
index 760f517431..e7519e94f9 100644
--- a/actionview/lib/action_view/template/handlers/raw.rb
+++ b/actionview/lib/action_view/template/handlers/raw.rb
@@ -2,7 +2,7 @@ module ActionView
module Template::Handlers
class Raw
def call(template)
- "#{template.source.inspect};"
+ "#{template.source.inspect}.html_safe;"
end
end
end
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
index 0321f819b5..0ffae10432 100644
--- a/actionview/lib/action_view/template/html.rb
+++ b/actionview/lib/action_view/template/html.rb
@@ -11,12 +11,10 @@ module ActionView #:nodoc:
end
def identifier
- 'html template'
+ "html template"
end
- def inspect
- 'html template'
- end
+ alias_method :inspect, :identifier
def to_str
ERB::Util.h(@string)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 8a675cd521..5a2948d5a9 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -37,15 +37,15 @@ module ActionView
class Cache #:nodoc:
class SmallCache < Concurrent::Map
def initialize(options = {})
- super(options.merge(:initial_capacity => 2))
+ super(options.merge(initial_capacity: 2))
end
end
# preallocate all the default blocks for performance/memory consumption reasons
- PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new}
- PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)}
- NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)}
- KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)}
+ PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
+ PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
+ NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
+ KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
# usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
NO_TEMPLATES = [].freeze
@@ -55,6 +55,10 @@ module ActionView
@query_cache = SmallCache.new
end
+ def inspect
+ "#<#{self.class.name}:0x#{(object_id << 1).to_s(16)} keys=#{@data.size} queries=#{@query_cache.size}>"
+ end
+
# Cache the templates returned by the block
def cache(key, name, prefix, partial, locals)
if Resolver.caching?
@@ -84,24 +88,41 @@ module ActionView
@query_cache.clear
end
- private
+ # Get the cache size. Do not call this
+ # method. This method is not guaranteed to be here ever.
+ def size # :nodoc:
+ size = 0
+ @data.each_value do |v1|
+ v1.each_value do |v2|
+ v2.each_value do |v3|
+ v3.each_value do |v4|
+ size += v4.size
+ end
+ end
+ end
+ end
- def canonical_no_templates(templates)
- templates.empty? ? NO_TEMPLATES : templates
+ size + @query_cache.size
end
- def templates_have_changed?(cached_templates, fresh_templates)
- # if either the old or new template list is empty, we don't need to (and can't)
- # compare modification times, and instead just check whether the lists are different
- if cached_templates.blank? || fresh_templates.blank?
- return fresh_templates.blank? != cached_templates.blank?
+ private
+
+ def canonical_no_templates(templates)
+ templates.empty? ? NO_TEMPLATES : templates
end
- cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
+ def templates_have_changed?(cached_templates, fresh_templates)
+ # if either the old or new template list is empty, we don't need to (and can't)
+ # compare modification times, and instead just check whether the lists are different
+ if cached_templates.blank? || fresh_templates.blank?
+ return fresh_templates.blank? != cached_templates.blank?
+ end
- # if a template has changed, it will be now be newer than all the cached templates
- fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
- end
+ cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
+
+ # if a template has changed, it will be now be newer than all the cached templates
+ fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
+ end
end
cattr_accessor :caching
@@ -183,7 +204,7 @@ module ActionView
# An abstract class that implements a Resolver with path semantics.
class PathResolver < Resolver #:nodoc:
- EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." }
+ EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
def initialize(pattern=nil)
@@ -193,89 +214,93 @@ module ActionView
private
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
- path = Path.build(name, prefix, partial)
- query(path, details, details[:formats], outside_app_allowed)
- end
+ def find_templates(name, prefix, partial, details, outside_app_allowed = false)
+ path = Path.build(name, prefix, partial)
+ query(path, details, details[:formats], outside_app_allowed)
+ end
- def query(path, details, formats, outside_app_allowed)
- query = build_query(path, details)
+ def query(path, details, formats, outside_app_allowed)
+ query = build_query(path, details)
- template_paths = find_template_paths(query)
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
+ template_paths = find_template_paths(query)
+ template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
- template_paths.map do |template|
- handler, format, variant = extract_handler_and_format_and_variant(template, formats)
- contents = File.binread(template)
+ template_paths.map do |template|
+ handler, format, variant = extract_handler_and_format_and_variant(template, formats)
+ contents = File.binread(template)
- Template.new(contents, File.expand_path(template), handler,
- :virtual_path => path.virtual,
- :format => format,
- :variant => variant,
- :updated_at => mtime(template)
- )
+ Template.new(contents, File.expand_path(template), handler,
+ virtual_path: path.virtual,
+ format: format,
+ variant: variant,
+ updated_at: mtime(template)
+ )
+ end
end
- end
- def reject_files_external_to_app(files)
- files.reject { |filename| !inside_path?(@path, filename) }
- end
+ def reject_files_external_to_app(files)
+ files.reject { |filename| !inside_path?(@path, filename) }
+ end
+
+ def find_template_paths(query)
+ Dir[query].uniq.reject do |filename|
+ File.directory?(filename) ||
+ # deals with case-insensitive file systems.
+ !File.fnmatch(query, filename, File::FNM_EXTGLOB)
+ end
+ end
- def find_template_paths(query)
- Dir[query].reject do |filename|
- File.directory?(filename) ||
- # deals with case-insensitive file systems.
- !File.fnmatch(query, filename, File::FNM_EXTGLOB)
+ def inside_path?(path, filename)
+ filename = File.expand_path(filename)
+ path = File.join(path, "")
+ filename.start_with?(path)
end
- end
- def inside_path?(path, filename)
- filename = File.expand_path(filename)
- path = File.join(path, '')
- filename.start_with?(path)
- end
+ # Helper for building query glob string based on resolver's pattern.
+ def build_query(path, details)
+ query = @pattern.dup
- # Helper for building query glob string based on resolver's pattern.
- def build_query(path, details)
- query = @pattern.dup
+ prefix = path.prefix.empty? ? "" : "#{escape_entry(path.prefix)}\\1"
+ query.gsub!(/:prefix(\/)?/, prefix)
- prefix = path.prefix.empty? ? '' : "#{escape_entry(path.prefix)}\\1"
- query.gsub!(/:prefix(\/)?/, prefix)
+ partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
+ query.gsub!(/:action/, partial)
- partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
- query.gsub!(/:action/, partial)
+ details.each do |ext, candidates|
+ if ext == :variants && candidates == :any
+ query.gsub!(/:#{ext}/, "*")
+ else
+ query.gsub!(/:#{ext}/, "{#{candidates.compact.uniq.join(',')}}")
+ end
+ end
- details.each do |ext, variants|
- query.gsub!(/:#{ext}/, "{#{variants.compact.uniq.join(',')}}")
+ File.expand_path(query, @path)
end
- File.expand_path(query, @path)
- end
-
- def escape_entry(entry)
- entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
- end
+ def escape_entry(entry)
+ entry.gsub(/[*?{}\[\]]/, '\\\\\\&'.freeze)
+ end
- # Returns the file mtime from the filesystem.
- def mtime(p)
- File.mtime(p)
- end
+ # Returns the file mtime from the filesystem.
+ def mtime(p)
+ File.mtime(p)
+ end
- # Extract handler, formats and variant from path. If a format cannot be found neither
- # from the path, or the handler, we should return the array of formats given
- # to the resolver.
- def extract_handler_and_format_and_variant(path, default_formats)
- pieces = File.basename(path).split('.'.freeze)
- pieces.shift
+ # Extract handler, formats and variant from path. If a format cannot be found neither
+ # from the path, or the handler, we should return the array of formats given
+ # to the resolver.
+ def extract_handler_and_format_and_variant(path, default_formats)
+ pieces = File.basename(path).split(".".freeze)
+ pieces.shift
- extension = pieces.pop
+ extension = pieces.pop
- handler = Template.handler_for_extension(extension)
- format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
- format &&= Template::Types[format]
+ handler = Template.handler_for_extension(extension)
+ format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
+ format &&= Template::Types[format]
- [handler, format, variant]
- end
+ [handler, format, variant]
+ end
end
# A resolver that loads files from the filesystem. It allows setting your own
@@ -340,7 +365,11 @@ module ActionView
query = escape_entry(File.join(@path, path))
exts = EXTENSIONS.map do |ext, prefix|
- "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ if ext == :variants && details[ext] == :any
+ "{#{prefix}*,}"
+ else
+ "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}"
+ end
end.join
query + exts
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index 04f5b8d17a..e8d4e18f04 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -11,12 +11,10 @@ module ActionView #:nodoc:
end
def identifier
- 'text template'
+ "text template"
end
- def inspect
- 'text template'
- end
+ alias_method :inspect, :identifier
def to_str
@string
diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb
index c233d06ccb..21959a3798 100644
--- a/actionview/lib/action_view/template/types.rb
+++ b/actionview/lib/action_view/template/types.rb
@@ -1,5 +1,4 @@
-require 'set'
-require 'active_support/core_ext/module/attribute_accessors'
+require "active_support/core_ext/module/attribute_accessors"
module ActionView
class Template
diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb
index 120962b5aa..3eb1ac0826 100644
--- a/actionview/lib/action_view/test_case.rb
+++ b/actionview/lib/action_view/test_case.rb
@@ -1,9 +1,9 @@
-require 'active_support/core_ext/module/remove_method'
-require 'action_controller'
-require 'action_controller/test_case'
-require 'action_view'
+require "active_support/core_ext/module/remove_method"
+require "action_controller"
+require "action_controller/test_case"
+require "action_view"
-require 'rails-dom-testing'
+require "rails-dom-testing"
module ActionView
# = Action View Test Case
@@ -24,10 +24,10 @@ module ActionView
def initialize
super
self.class.controller_path = ""
- @request = ActionController::TestRequest.create
+ @request = ActionController::TestRequest.create(self.class)
@response = ActionDispatch::TestResponse.new
- @request.env.delete('PATH_INFO')
+ @request.env.delete("PATH_INFO")
@params = ActionController::Parameters.new
end
end
@@ -49,7 +49,7 @@ module ActionView
include ActiveSupport::Testing::ConstantLookup
- delegate :lookup_context, :to => :controller
+ delegate :lookup_context, to: :controller
attr_accessor :controller, :output_buffer, :rendered
module ClassMethods
@@ -96,7 +96,6 @@ module ActionView
helper(helper_class) if helper_class
include _helpers
end
-
end
def setup_with_controller
@@ -104,8 +103,8 @@ module ActionView
@request = @controller.request
# 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 = ''
+ @output_buffer = ActiveSupport::SafeBuffer.new ""
+ @rendered = ""
make_test_case_available_to_view!
say_no_to_protect_against_forgery!
@@ -146,13 +145,14 @@ module ActionView
def view_rendered?(view, expected_locals)
locals_for(view).any? do |actual_locals|
- expected_locals.all? {|key, value| value == actual_locals[key] }
+ expected_locals.all? { |key, value| value == actual_locals[key] }
end
end
end
included do
setup :setup_with_controller
+ ActiveSupport.run_load_hooks(:action_view_test_case, self)
end
private
diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb
index 2664aca991..f072664535 100644
--- a/actionview/lib/action_view/testing/resolvers.rb
+++ b/actionview/lib/action_view/testing/resolvers.rb
@@ -1,4 +1,4 @@
-require 'action_view/template/resolver'
+require "action_view/template/resolver"
module ActionView #:nodoc:
# Use FixtureResolver in your tests to simulate the presence of files on the
@@ -14,7 +14,7 @@ module ActionView #:nodoc:
end
def to_s
- @hash.keys.join(', ')
+ @hash.keys.join(", ")
end
private
@@ -22,32 +22,31 @@ module ActionView #:nodoc:
def query(path, exts, formats, _)
query = ""
EXTENSIONS.each_key do |ext|
- query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)'
+ query << "(" << exts[ext].map { |e| e && Regexp.escape(".#{e}") }.join("|") << "|)"
end
query = /^(#{Regexp.escape(path)})#{query}$/
templates = []
@hash.each do |_path, array|
source, updated_at = array
- next unless _path =~ query
+ next unless query.match?(_path)
handler, format, variant = extract_handler_and_format_and_variant(_path, formats)
templates << Template.new(source, _path, handler,
- :virtual_path => path.virtual,
- :format => format,
- :variant => variant,
- :updated_at => updated_at
+ virtual_path: path.virtual,
+ format: format,
+ variant: variant,
+ updated_at: updated_at
)
end
- templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
+ templates.sort_by { |t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size }
end
end
class NullResolver < PathResolver
def query(path, exts, formats, _)
handler, format, variant = extract_handler_and_format_and_variant(path, formats)
- [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, :virtual_path => path.virtual, :format => format, :variant => variant)]
+ [ActionView::Template.new("Template generated by Null Resolver", path.virtual, handler, virtual_path: path.virtual, format: format, variant: variant)]
end
end
end
-
diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb
index f55d3fdaef..315404864d 100644
--- a/actionview/lib/action_view/version.rb
+++ b/actionview/lib/action_view/version.rb
@@ -1,4 +1,4 @@
-require_relative 'gem_version'
+require_relative "gem_version"
module ActionView
# Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt>
diff --git a/actionview/lib/action_view/view_paths.rb b/actionview/lib/action_view/view_paths.rb
index 37722013ce..b5cde5b43f 100644
--- a/actionview/lib/action_view/view_paths.rb
+++ b/actionview/lib/action_view/view_paths.rb
@@ -1,5 +1,3 @@
-require 'action_view/base'
-
module ActionView
module ViewPaths
extend ActiveSupport::Concern
@@ -10,8 +8,8 @@ module ActionView
self._view_paths.freeze
end
- delegate :template_exists?, :view_paths, :formats, :formats=,
- :locale, :locale=, :to => :lookup_context
+ delegate :template_exists?, :any_templates?, :view_paths, :formats, :formats=,
+ :locale, :locale=, to: :lookup_context
module ClassMethods
def _prefixes # :nodoc:
@@ -24,11 +22,11 @@ module ActionView
private
- # Override this method in your controller if you want to change paths prefixes for finding views.
- # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
- def local_prefixes
- [controller_path]
- end
+ # Override this method in your controller if you want to change paths prefixes for finding views.
+ # Prefixes defined here will still be added to parents' <tt>._prefixes</tt>.
+ def local_prefixes
+ [controller_path]
+ end
end
# The prefixes used in render "foo" shortcuts.
@@ -45,7 +43,7 @@ module ActionView
end
def details_for_lookup
- { }
+ {}
end
def append_view_path(path)