aboutsummaryrefslogtreecommitdiffstats
path: root/actionview/lib/action_view/template
diff options
context:
space:
mode:
Diffstat (limited to 'actionview/lib/action_view/template')
-rw-r--r--actionview/lib/action_view/template/error.rb22
-rw-r--r--actionview/lib/action_view/template/handlers.rb6
-rw-r--r--actionview/lib/action_view/template/handlers/erb/erubi.rb6
-rw-r--r--actionview/lib/action_view/template/html.rb19
-rw-r--r--actionview/lib/action_view/template/inline.rb22
-rw-r--r--actionview/lib/action_view/template/raw_file.rb28
-rw-r--r--actionview/lib/action_view/template/resolver.rb196
-rw-r--r--actionview/lib/action_view/template/sources.rb13
-rw-r--r--actionview/lib/action_view/template/sources/file.rb17
-rw-r--r--actionview/lib/action_view/template/text.rb8
10 files changed, 206 insertions, 131 deletions
diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb
index 4e3c02e05e..d0ea03e228 100644
--- a/actionview/lib/action_view/template/error.rb
+++ b/actionview/lib/action_view/template/error.rb
@@ -109,7 +109,7 @@ module ActionView
end
end
- def annoted_source_code
+ def annotated_source_code
source_extract(4)
end
@@ -138,4 +138,24 @@ module ActionView
end
TemplateError = Template::Error
+
+ class SyntaxErrorInTemplate < TemplateError #:nodoc
+ def initialize(template, offending_code_string)
+ @offending_code_string = offending_code_string
+ super(template)
+ end
+
+ def message
+ <<~MESSAGE
+ Encountered a syntax error while rendering template: check #{@offending_code_string}
+ MESSAGE
+ end
+
+ def annotated_source_code
+ @offending_code_string.split("\n").map.with_index(1) { |line, index|
+ indentation = " " * 4
+ "#{index}:#{indentation}#{line}"
+ }
+ end
+ end
end
diff --git a/actionview/lib/action_view/template/handlers.rb b/actionview/lib/action_view/template/handlers.rb
index ddaac7a100..6450513003 100644
--- a/actionview/lib/action_view/template/handlers.rb
+++ b/actionview/lib/action_view/template/handlers.rb
@@ -45,12 +45,12 @@ module ActionView #:nodoc:
unless params.find_all { |type, _| type == :req || type == :opt }.length >= 2
ActiveSupport::Deprecation.warn <<~eowarn
- Single arity template handlers are deprecated. Template handlers must
+ Single arity template handlers are deprecated. Template handlers must
now accept two parameters, the view object and the source for the view object.
Change:
- >> #{handler.class}#call(#{params.map(&:last).join(", ")})
+ >> #{handler}.call(#{params.map(&:last).join(", ")})
To:
- >> #{handler.class}#call(#{params.map(&:last).join(", ")}, source)
+ >> #{handler}.call(#{params.map(&:last).join(", ")}, source)
eowarn
handler = LegacyHandlerWrapper.new(handler)
end
diff --git a/actionview/lib/action_view/template/handlers/erb/erubi.rb b/actionview/lib/action_view/template/handlers/erb/erubi.rb
index 15ca202024..e602aa117a 100644
--- a/actionview/lib/action_view/template/handlers/erb/erubi.rb
+++ b/actionview/lib/action_view/template/handlers/erb/erubi.rb
@@ -25,9 +25,9 @@ module ActionView
src = @src
view = Class.new(ActionView::Base) {
include action_view_erb_handler_context._routes.url_helpers
- class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", @filename || "(erubi)", 0)
- }.with_context(action_view_erb_handler_context)
- view.run(:_template, {}, ActionView::OutputBuffer.new)
+ class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
+ }.empty
+ view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
end
private
diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb
index a262c6d9ad..ecd1c31e79 100644
--- a/actionview/lib/action_view/template/html.rb
+++ b/actionview/lib/action_view/template/html.rb
@@ -1,15 +1,21 @@
# frozen_string_literal: true
+require "active_support/deprecation"
+
module ActionView #:nodoc:
# = Action View HTML Template
class Template #:nodoc:
class HTML #:nodoc:
- attr_accessor :type
+ attr_reader :type
def initialize(string, type = nil)
+ unless type
+ ActiveSupport::Deprecation.warn "ActionView::Template::HTML#initialize requires a type parameter"
+ type = :html
+ end
+
@string = string.to_s
- @type = Types[type] || type if type
- @type ||= Types[:html]
+ @type = type
end
def identifier
@@ -26,9 +32,12 @@ module ActionView #:nodoc:
to_str
end
- def formats
- [@type.respond_to?(:ref) ? @type.ref : @type.to_s]
+ def format
+ @type
end
+
+ def formats; Array(format); end
+ deprecate :formats
end
end
end
diff --git a/actionview/lib/action_view/template/inline.rb b/actionview/lib/action_view/template/inline.rb
new file mode 100644
index 0000000000..44658487ea
--- /dev/null
+++ b/actionview/lib/action_view/template/inline.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module ActionView #:nodoc:
+ class Template #:nodoc:
+ class Inline < Template #:nodoc:
+ # This finalizer is needed (and exactly with a proc inside another proc)
+ # otherwise templates leak in development.
+ Finalizer = proc do |method_name, mod| # :nodoc:
+ proc do
+ mod.module_eval do
+ remove_possible_method method_name
+ end
+ end
+ end
+
+ def compile(mod)
+ super
+ ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/raw_file.rb b/actionview/lib/action_view/template/raw_file.rb
new file mode 100644
index 0000000000..623351305f
--- /dev/null
+++ b/actionview/lib/action_view/template/raw_file.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module ActionView #:nodoc:
+ # = Action View RawFile Template
+ class Template #:nodoc:
+ class RawFile #:nodoc:
+ attr_accessor :type, :format
+
+ def initialize(filename)
+ @filename = filename.to_s
+ extname = ::File.extname(filename).delete(".")
+ @type = Template::Types[extname] || Template::Types[:text]
+ @format = @type.symbol
+ end
+
+ def identifier
+ @filename
+ end
+
+ def render(*args)
+ ::File.read(@filename)
+ end
+
+ def formats; Array(format); end
+ deprecate :formats
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 3b4594942b..ce53eb046d 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -63,26 +63,11 @@ module ActionView
# Cache the templates returned by the block
def cache(key, name, prefix, partial, locals)
- if Resolver.caching?
- @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
- else
- fresh_templates = yield
- cached_templates = @data[key][name][prefix][partial][locals]
-
- if templates_have_changed?(cached_templates, fresh_templates)
- @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
- else
- cached_templates || NO_TEMPLATES
- end
- end
+ @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
end
def cache_query(query) # :nodoc:
- if Resolver.caching?
- @query_cache[query] ||= canonical_no_templates(yield)
- else
- yield
- end
+ @query_cache[query] ||= canonical_no_templates(yield)
end
def clear
@@ -112,19 +97,6 @@ module ActionView
def canonical_no_templates(templates)
templates.empty? ? NO_TEMPLATES : templates
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?
- 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, default: true
@@ -143,35 +115,33 @@ module ActionView
# Normalizes the arguments and passes it on to find_templates.
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
- cached(key, [name, prefix, partial], details, locals) do
- find_templates(name, prefix, partial, details)
- end
- end
+ locals = locals.map(&:to_s).sort!.freeze
- def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
cached(key, [name, prefix, partial], details, locals) do
- find_templates(name, prefix, partial, details, true)
+ _find_all(name, prefix, partial, details, key, locals)
end
end
+ alias :find_all_anywhere :find_all
+ deprecate :find_all_anywhere
+
def find_all_with_query(query) # :nodoc:
@cache.cache_query(query) { find_template_paths(File.join(@path, query)) }
end
private
+ def _find_all(name, prefix, partial, details, key, locals)
+ find_templates(name, prefix, partial, details, locals)
+ end
+
delegate :caching?, to: :class
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
# normalized.
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
- raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, outside_app_allowed = false) method"
- end
-
- # Helpers that builds a path. Useful for building virtual paths.
- def build_path(name, prefix, partial)
- Path.build(name, prefix, partial)
+ def find_templates(name, prefix, partial, details, locals = [])
+ raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
end
# Handles templates caching. If a key is given and caching is on
@@ -180,25 +150,13 @@ module ActionView
# resolver is fresher before returning it.
def cached(key, path_info, details, locals)
name, prefix, partial = path_info
- locals = locals.map(&:to_s).sort!
if key
@cache.cache(key, name, prefix, partial, locals) do
- decorate(yield, path_info, details, locals)
+ yield
end
else
- decorate(yield, path_info, details, locals)
- end
- end
-
- # Ensures all the resolver information is set in the template.
- def decorate(templates, path_info, details, locals)
- cached = nil
- templates.each do |t|
- t.locals = locals
- t.formats = details[:formats] || [:html] if t.formats.empty?
- t.variants = details[:variants] || [] if t.variants.empty?
- t.virtual_path ||= (cached ||= build_path(*path_info))
+ yield
end
end
end
@@ -209,33 +167,60 @@ module ActionView
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
def initialize(pattern = nil)
- @pattern = pattern || DEFAULT_PATTERN
+ if pattern
+ ActiveSupport::Deprecation.warn "Specifying a custom path for #{self.class} is deprecated. Implement a custom Resolver subclass instead."
+ @pattern = pattern
+ else
+ @pattern = DEFAULT_PATTERN
+ end
+ @unbound_templates = Concurrent::Map.new
+ super()
+ end
+
+ def clear_cache
+ @unbound_templates.clear
super()
end
private
- def find_templates(name, prefix, partial, details, outside_app_allowed = false)
+ def _find_all(name, prefix, partial, details, key, locals)
path = Path.build(name, prefix, partial)
- query(path, details, details[:formats], outside_app_allowed)
+ query(path, details, details[:formats], locals, cache: !!key)
end
- def query(path, details, formats, outside_app_allowed)
+ def query(path, details, formats, locals, cache:)
template_paths = find_template_paths_from_details(path, details)
- template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
+ template_paths = reject_files_external_to_app(template_paths)
template_paths.map do |template|
- handler, format, variant = extract_handler_and_format_and_variant(template)
-
- FileTemplate.new(File.expand_path(template), handler,
- virtual_path: path.virtual,
- format: format,
- variant: variant,
- updated_at: mtime(template)
- )
+ unbound_template =
+ if cache
+ @unbound_templates.compute_if_absent([template, path.virtual]) do
+ build_unbound_template(template, path.virtual)
+ end
+ else
+ build_unbound_template(template, path.virtual)
+ end
+
+ unbound_template.bind_locals(locals)
end
end
+ def build_unbound_template(template, virtual_path)
+ handler, format, variant = extract_handler_and_format_and_variant(template)
+ source = Template::Sources::File.new(template)
+
+ UnboundTemplate.new(
+ source,
+ template,
+ handler,
+ virtual_path: virtual_path,
+ format: format,
+ variant: variant,
+ )
+ end
+
def reject_files_external_to_app(files)
files.reject { |filename| !inside_path?(@path, filename) }
end
@@ -284,11 +269,6 @@ module ActionView
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
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.
@@ -300,51 +280,25 @@ module ActionView
handler = Template.handler_for_extension(extension)
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
- format &&= Template::Types[format]
+ format = if format
+ Template::Types[format]&.ref
+ else
+ if handler.respond_to?(:default_format) # default_format can return nil
+ handler.default_format
+ else
+ nil
+ end
+ end
+ # Template::Types[format] and handler.default_format can return nil
[handler, format, variant]
end
end
- # A resolver that loads files from the filesystem. It allows setting your own
- # resolving pattern. Such pattern can be a glob string supported by some variables.
- #
- # ==== Examples
- #
- # Default pattern, loads views the same way as previous versions of rails, eg. when you're
- # looking for <tt>users/new</tt> it will produce query glob: <tt>users/new{.{en},}{.{html,js},}{.{erb,haml},}</tt>
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
- #
- # This one allows you to keep files with different formats in separate subdirectories,
- # eg. <tt>users/new.html</tt> will be loaded from <tt>users/html/new.erb</tt> or <tt>users/new.html.erb</tt>,
- # <tt>users/new.js</tt> from <tt>users/js/new.erb</tt> or <tt>users/new.js.erb</tt>, etc.
- #
- # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
- #
- # If you don't specify a pattern then the default will be used.
- #
- # In order to use any of the customized resolvers above in a Rails application, you just need
- # to configure ActionController::Base.view_paths in an initializer, for example:
- #
- # ActionController::Base.view_paths = FileSystemResolver.new(
- # Rails.root.join("app/views"),
- # ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
- # )
- #
- # ==== Pattern format and variables
- #
- # Pattern has to be a valid glob string, and it allows you to use the
- # following variables:
- #
- # * <tt>:prefix</tt> - usually the controller path
- # * <tt>:action</tt> - name of the action
- # * <tt>:locale</tt> - possible locale versions
- # * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
- # * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
- # * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
- #
+ # A resolver that loads files from the filesystem.
class FileSystemResolver < PathResolver
+ attr_reader :path
+
def initialize(path, pattern = nil)
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
super(pattern)
@@ -364,6 +318,10 @@ module ActionView
# An Optimized resolver for Rails' most common case.
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
+ def initialize(path)
+ super(path)
+ end
+
private
def find_template_paths_from_details(path, details)
@@ -419,12 +377,18 @@ module ActionView
# The same as FileSystemResolver but does not allow templates to store
# a virtual path since it is invalid for such resolvers.
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
+ private_class_method :new
+
def self.instances
[new(""), new("/")]
end
- def decorate(*)
- super.each { |t| t.virtual_path = nil }
+ def build_unbound_template(template, _)
+ super(template, nil)
+ end
+
+ def reject_files_external_to_app(files)
+ files
end
end
end
diff --git a/actionview/lib/action_view/template/sources.rb b/actionview/lib/action_view/template/sources.rb
new file mode 100644
index 0000000000..8b89535741
--- /dev/null
+++ b/actionview/lib/action_view/template/sources.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module ActionView
+ class Template
+ module Sources
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :File
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/sources/file.rb b/actionview/lib/action_view/template/sources/file.rb
new file mode 100644
index 0000000000..2d3a7dec7f
--- /dev/null
+++ b/actionview/lib/action_view/template/sources/file.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module ActionView
+ class Template
+ module Sources
+ class File
+ def initialize(filename)
+ @filename = filename
+ end
+
+ def to_s
+ ::File.binread @filename
+ end
+ end
+ end
+ end
+end
diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb
index f8d6c2811f..c5fd55f1b3 100644
--- a/actionview/lib/action_view/template/text.rb
+++ b/actionview/lib/action_view/template/text.rb
@@ -8,7 +8,6 @@ module ActionView #:nodoc:
def initialize(string)
@string = string.to_s
- @type = Types[:text]
end
def identifier
@@ -25,9 +24,12 @@ module ActionView #:nodoc:
to_str
end
- def formats
- [@type.ref]
+ def format
+ :text
end
+
+ def formats; Array(format); end
+ deprecate :formats
end
end
end