actionview 5.1.4 → 6.1.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +199 -168
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -5
- data/lib/action_view.rb +10 -4
- data/lib/action_view/base.rb +87 -23
- data/lib/action_view/buffers.rb +17 -0
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +7 -11
- data/lib/action_view/dependency_tracker.rb +12 -4
- data/lib/action_view/digestor.rb +24 -23
- data/lib/action_view/flows.rb +2 -1
- data/lib/action_view/gem_version.rb +4 -2
- data/lib/action_view/helpers.rb +4 -2
- data/lib/action_view/helpers/active_model_helper.rb +9 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +220 -57
- data/lib/action_view/helpers/asset_url_helper.rb +28 -23
- data/lib/action_view/helpers/atom_feed_helper.rb +5 -2
- data/lib/action_view/helpers/cache_helper.rb +39 -28
- data/lib/action_view/helpers/capture_helper.rb +13 -7
- data/lib/action_view/helpers/controller_helper.rb +3 -1
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +5 -3
- data/lib/action_view/helpers/date_helper.rb +78 -33
- data/lib/action_view/helpers/debug_helper.rb +4 -2
- data/lib/action_view/helpers/form_helper.rb +357 -106
- data/lib/action_view/helpers/form_options_helper.rb +45 -39
- data/lib/action_view/helpers/form_tag_helper.rb +42 -27
- data/lib/action_view/helpers/javascript_helper.rb +28 -12
- data/lib/action_view/helpers/number_helper.rb +16 -8
- data/lib/action_view/helpers/output_safety_helper.rb +3 -1
- data/lib/action_view/helpers/rendering_helper.rb +20 -9
- data/lib/action_view/helpers/sanitize_helper.rb +15 -19
- data/lib/action_view/helpers/tag_helper.rb +100 -24
- data/lib/action_view/helpers/tags.rb +3 -1
- data/lib/action_view/helpers/tags/base.rb +30 -21
- data/lib/action_view/helpers/tags/check_box.rb +3 -2
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +2 -1
- data/lib/action_view/helpers/tags/collection_helpers.rb +2 -1
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +2 -1
- data/lib/action_view/helpers/tags/collection_select.rb +3 -1
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +5 -4
- data/lib/action_view/helpers/tags/datetime_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +3 -1
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +6 -5
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +2 -0
- data/lib/action_view/helpers/tags/placeholderable.rb +2 -0
- data/lib/action_view/helpers/tags/radio_button.rb +3 -2
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +2 -0
- data/lib/action_view/helpers/tags/select.rb +4 -3
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +3 -1
- data/lib/action_view/helpers/tags/text_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +3 -6
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/text_helper.rb +11 -10
- data/lib/action_view/helpers/translation_helper.rb +102 -52
- data/lib/action_view/helpers/url_helper.rb +150 -32
- data/lib/action_view/layouts.rb +15 -15
- data/lib/action_view/log_subscriber.rb +32 -15
- data/lib/action_view/lookup_context.rb +67 -39
- data/lib/action_view/model_naming.rb +2 -0
- data/lib/action_view/path_set.rb +5 -12
- data/lib/action_view/railtie.rb +46 -21
- data/lib/action_view/record_identifier.rb +4 -3
- data/lib/action_view/renderer/abstract_renderer.rb +144 -11
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer.rb +33 -283
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +64 -17
- data/lib/action_view/renderer/renderer.rb +61 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +14 -8
- data/lib/action_view/renderer/template_renderer.rb +36 -26
- data/lib/action_view/rendering.rb +57 -38
- data/lib/action_view/routing_url_for.rb +15 -12
- data/lib/action_view/tasks/cache_digests.rake +2 -0
- data/lib/action_view/template.rb +69 -76
- data/lib/action_view/template/error.rb +32 -18
- data/lib/action_view/template/handlers.rb +4 -2
- data/lib/action_view/template/handlers/builder.rb +5 -6
- data/lib/action_view/template/handlers/erb.rb +20 -19
- data/lib/action_view/template/handlers/erb/erubi.rb +17 -9
- data/lib/action_view/template/handlers/html.rb +3 -1
- data/lib/action_view/template/handlers/raw.rb +4 -2
- data/lib/action_view/template/html.rb +8 -7
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +194 -152
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +5 -4
- data/lib/action_view/template/types.rb +3 -1
- data/lib/action_view/test_case.rb +38 -30
- data/lib/action_view/testing/resolvers.rb +20 -27
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +2 -0
- data/lib/action_view/view_paths.rb +61 -40
- data/lib/assets/compiled/rails-ujs.js +84 -23
- metadata +34 -23
- data/lib/action_view/helpers/record_tag_helper.rb +0 -21
- data/lib/action_view/template/handlers/erb/deprecated_erubis.rb +0 -9
- data/lib/action_view/template/handlers/erb/erubis.rb +0 -81
@@ -1,29 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
class Template
|
3
5
|
module Handlers
|
4
|
-
autoload :Erubis, "action_view/template/handlers/erb/deprecated_erubis"
|
5
|
-
|
6
6
|
class ERB
|
7
7
|
autoload :Erubi, "action_view/template/handlers/erb/erubi"
|
8
|
-
autoload :Erubis, "action_view/template/handlers/erb/erubis"
|
9
8
|
|
10
9
|
# Specify trim mode for the ERB compiler. Defaults to '-'.
|
11
10
|
# See ERB documentation for suitable values.
|
12
|
-
class_attribute :erb_trim_mode
|
13
|
-
self.erb_trim_mode = "-"
|
11
|
+
class_attribute :erb_trim_mode, default: "-"
|
14
12
|
|
15
13
|
# Default implementation used.
|
16
|
-
class_attribute :erb_implementation
|
17
|
-
self.erb_implementation = Erubi
|
14
|
+
class_attribute :erb_implementation, default: Erubi
|
18
15
|
|
19
16
|
# Do not escape templates of these mime types.
|
20
|
-
class_attribute :
|
21
|
-
self.escape_whitelist = ["text/plain"]
|
17
|
+
class_attribute :escape_ignore_list, default: ["text/plain"]
|
22
18
|
|
23
19
|
ENCODING_TAG = Regexp.new("\\A(<%#{ENCODING_FLAG}-?%>)[ \\t]*")
|
24
20
|
|
25
|
-
def self.call(template)
|
26
|
-
new.call(template)
|
21
|
+
def self.call(template, source)
|
22
|
+
new.call(template, source)
|
27
23
|
end
|
28
24
|
|
29
25
|
def supports_streaming?
|
@@ -34,30 +30,35 @@ module ActionView
|
|
34
30
|
true
|
35
31
|
end
|
36
32
|
|
37
|
-
def call(template)
|
33
|
+
def call(template, source)
|
38
34
|
# First, convert to BINARY, so in case the encoding is
|
39
35
|
# wrong, we can still find an encoding tag
|
40
36
|
# (<%# encoding %>) inside the String using a regular
|
41
37
|
# expression
|
42
|
-
template_source =
|
38
|
+
template_source = source.b
|
43
39
|
|
44
40
|
erb = template_source.gsub(ENCODING_TAG, "")
|
45
41
|
encoding = $2
|
46
42
|
|
47
|
-
erb.force_encoding valid_encoding(
|
43
|
+
erb.force_encoding valid_encoding(source.dup, encoding)
|
48
44
|
|
49
45
|
# Always make sure we return a String in the default_internal
|
50
46
|
erb.encode!
|
51
47
|
|
52
|
-
|
53
|
-
|
54
|
-
escape: (self.class.escape_whitelist.include? template.type),
|
48
|
+
options = {
|
49
|
+
escape: (self.class.escape_ignore_list.include? template.type),
|
55
50
|
trim: (self.class.erb_trim_mode == "-")
|
56
|
-
|
51
|
+
}
|
52
|
+
|
53
|
+
if ActionView::Base.annotate_rendered_view_with_filenames && template.format == :html
|
54
|
+
options[:preamble] = "@output_buffer.safe_append='<!-- BEGIN #{template.short_identifier} -->';"
|
55
|
+
options[:postamble] = "@output_buffer.safe_append='<!-- END #{template.short_identifier} -->';@output_buffer.to_s"
|
56
|
+
end
|
57
|
+
|
58
|
+
self.class.erb_implementation.new(erb, options).src
|
57
59
|
end
|
58
60
|
|
59
61
|
private
|
60
|
-
|
61
62
|
def valid_encoding(string, encoding)
|
62
63
|
# If a magic encoding comment was found, tag the
|
63
64
|
# String with this encoding. This is for a case
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "erubi"
|
2
4
|
|
3
5
|
module ActionView
|
@@ -11,17 +13,23 @@ module ActionView
|
|
11
13
|
|
12
14
|
# Dup properties so that we don't modify argument
|
13
15
|
properties = Hash[properties]
|
14
|
-
|
15
|
-
properties[:
|
16
|
-
properties[:
|
16
|
+
|
17
|
+
properties[:bufvar] ||= "@output_buffer"
|
18
|
+
properties[:preamble] ||= ""
|
19
|
+
properties[:postamble] ||= "#{properties[:bufvar]}.to_s"
|
20
|
+
|
17
21
|
properties[:escapefunc] = ""
|
18
22
|
|
19
23
|
super
|
20
24
|
end
|
21
25
|
|
22
26
|
def evaluate(action_view_erb_handler_context)
|
23
|
-
|
24
|
-
|
27
|
+
src = @src
|
28
|
+
view = Class.new(ActionView::Base) {
|
29
|
+
include action_view_erb_handler_context._routes.url_helpers
|
30
|
+
class_eval("define_method(:_template) { |local_assigns, output_buffer| #{src} }", defined?(@filename) ? @filename : "(erubi)", 0)
|
31
|
+
}.empty
|
32
|
+
view._run(:_template, nil, {}, ActionView::OutputBuffer.new)
|
25
33
|
end
|
26
34
|
|
27
35
|
private
|
@@ -31,7 +39,7 @@ module ActionView
|
|
31
39
|
if text == "\n"
|
32
40
|
@newline_pending += 1
|
33
41
|
else
|
34
|
-
src << "
|
42
|
+
src << bufvar << ".safe_append='"
|
35
43
|
src << "\n" * @newline_pending if @newline_pending > 0
|
36
44
|
src << text.gsub(/['\\]/, '\\\\\&')
|
37
45
|
src << "'.freeze;"
|
@@ -46,9 +54,9 @@ module ActionView
|
|
46
54
|
flush_newline_if_pending(src)
|
47
55
|
|
48
56
|
if (indicator == "==") || @escape
|
49
|
-
src << "
|
57
|
+
src << bufvar << ".safe_expr_append="
|
50
58
|
else
|
51
|
-
src << "
|
59
|
+
src << bufvar << ".append="
|
52
60
|
end
|
53
61
|
|
54
62
|
if BLOCK_EXPR.match?(code)
|
@@ -70,7 +78,7 @@ module ActionView
|
|
70
78
|
|
71
79
|
def flush_newline_if_pending(src)
|
72
80
|
if @newline_pending > 0
|
73
|
-
src << "
|
81
|
+
src << bufvar << ".safe_append='#{"\n" * @newline_pending}'.freeze;"
|
74
82
|
@newline_pending = 0
|
75
83
|
end
|
76
84
|
end
|
@@ -1,13 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActionView #:nodoc:
|
2
4
|
# = Action View HTML Template
|
3
|
-
class Template
|
5
|
+
class Template #:nodoc:
|
4
6
|
class HTML #:nodoc:
|
5
|
-
|
7
|
+
attr_reader :type
|
6
8
|
|
7
|
-
def initialize(string, type
|
9
|
+
def initialize(string, type)
|
8
10
|
@string = string.to_s
|
9
|
-
@type =
|
10
|
-
@type ||= Types[:html]
|
11
|
+
@type = type
|
11
12
|
end
|
12
13
|
|
13
14
|
def identifier
|
@@ -24,8 +25,8 @@ module ActionView #:nodoc:
|
|
24
25
|
to_str
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
|
28
|
+
def format
|
29
|
+
@type
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
class Template #:nodoc:
|
5
|
+
class Inline < Template #:nodoc:
|
6
|
+
# This finalizer is needed (and exactly with a proc inside another proc)
|
7
|
+
# otherwise templates leak in development.
|
8
|
+
Finalizer = proc do |method_name, mod| # :nodoc:
|
9
|
+
proc do
|
10
|
+
mod.module_eval do
|
11
|
+
remove_possible_method method_name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def compile(mod)
|
17
|
+
super
|
18
|
+
ObjectSpace.define_finalizer(self, Finalizer[method_name, mod])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView #:nodoc:
|
4
|
+
# = Action View RawFile Template
|
5
|
+
class Template #:nodoc:
|
6
|
+
class RawFile #:nodoc:
|
7
|
+
attr_accessor :type, :format
|
8
|
+
|
9
|
+
def initialize(filename)
|
10
|
+
@filename = filename.to_s
|
11
|
+
extname = ::File.extname(filename).delete(".")
|
12
|
+
@type = Template::Types[extname] || Template::Types[:text]
|
13
|
+
@format = @type.symbol
|
14
|
+
end
|
15
|
+
|
16
|
+
def identifier
|
17
|
+
@filename
|
18
|
+
end
|
19
|
+
|
20
|
+
def render(*args)
|
21
|
+
::File.read(@filename)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
# = Action View Renderable Template for objects that respond to #render_in
|
5
|
+
class Template
|
6
|
+
class Renderable # :nodoc:
|
7
|
+
def initialize(renderable)
|
8
|
+
@renderable = renderable
|
9
|
+
end
|
10
|
+
|
11
|
+
def identifier
|
12
|
+
@renderable.class.name
|
13
|
+
end
|
14
|
+
|
15
|
+
def render(context, *args)
|
16
|
+
@renderable.render_in(context)
|
17
|
+
end
|
18
|
+
|
19
|
+
def format
|
20
|
+
@renderable.format
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "pathname"
|
2
4
|
require "active_support/core_ext/class"
|
3
5
|
require "active_support/core_ext/module/attribute_accessors"
|
@@ -14,7 +16,7 @@ module ActionView
|
|
14
16
|
alias_method :partial?, :partial
|
15
17
|
|
16
18
|
def self.build(name, prefix, partial)
|
17
|
-
virtual = ""
|
19
|
+
virtual = +""
|
18
20
|
virtual << "#{prefix}/" unless prefix.empty?
|
19
21
|
virtual << (partial ? "_#{name}" : name)
|
20
22
|
new name, prefix, partial, virtual
|
@@ -33,6 +35,41 @@ module ActionView
|
|
33
35
|
alias :to_s :to_str
|
34
36
|
end
|
35
37
|
|
38
|
+
class PathParser # :nodoc:
|
39
|
+
def build_path_regex
|
40
|
+
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
|
41
|
+
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
|
42
|
+
locales = "[a-z]{2}(?:-[A-Z]{2})?"
|
43
|
+
variants = "[^.]*"
|
44
|
+
|
45
|
+
%r{
|
46
|
+
\A
|
47
|
+
(?:(?<prefix>.*)/)?
|
48
|
+
(?<partial>_)?
|
49
|
+
(?<action>.*?)
|
50
|
+
(?:\.(?<locale>#{locales}))??
|
51
|
+
(?:\.(?<format>#{formats}))??
|
52
|
+
(?:\+(?<variant>#{variants}))??
|
53
|
+
(?:\.(?<handler>#{handlers}))?
|
54
|
+
\z
|
55
|
+
}x
|
56
|
+
end
|
57
|
+
|
58
|
+
def parse(path)
|
59
|
+
@regex ||= build_path_regex
|
60
|
+
match = @regex.match(path)
|
61
|
+
{
|
62
|
+
prefix: match[:prefix] || "",
|
63
|
+
action: match[:action],
|
64
|
+
partial: !!match[:partial],
|
65
|
+
locale: match[:locale]&.to_sym,
|
66
|
+
handler: match[:handler]&.to_sym,
|
67
|
+
format: match[:format]&.to_sym,
|
68
|
+
variant: match[:variant]
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
36
73
|
# Threadsafe template cache
|
37
74
|
class Cache #:nodoc:
|
38
75
|
class SmallCache < Concurrent::Map
|
@@ -41,13 +78,13 @@ module ActionView
|
|
41
78
|
end
|
42
79
|
end
|
43
80
|
|
44
|
-
#
|
81
|
+
# Preallocate all the default blocks for performance/memory consumption reasons
|
45
82
|
PARTIAL_BLOCK = lambda { |cache, partial| cache[partial] = SmallCache.new }
|
46
83
|
PREFIX_BLOCK = lambda { |cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK) }
|
47
84
|
NAME_BLOCK = lambda { |cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK) }
|
48
85
|
KEY_BLOCK = lambda { |cache, key| cache[key] = SmallCache.new(&NAME_BLOCK) }
|
49
86
|
|
50
|
-
#
|
87
|
+
# Usually a majority of template look ups return nothing, use this canonical preallocated array to save memory
|
51
88
|
NO_TEMPLATES = [].freeze
|
52
89
|
|
53
90
|
def initialize
|
@@ -56,31 +93,16 @@ module ActionView
|
|
56
93
|
end
|
57
94
|
|
58
95
|
def inspect
|
59
|
-
"
|
96
|
+
"#{to_s[0..-2]} keys=#{@data.size} queries=#{@query_cache.size}>"
|
60
97
|
end
|
61
98
|
|
62
99
|
# Cache the templates returned by the block
|
63
100
|
def cache(key, name, prefix, partial, locals)
|
64
|
-
|
65
|
-
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
66
|
-
else
|
67
|
-
fresh_templates = yield
|
68
|
-
cached_templates = @data[key][name][prefix][partial][locals]
|
69
|
-
|
70
|
-
if templates_have_changed?(cached_templates, fresh_templates)
|
71
|
-
@data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates)
|
72
|
-
else
|
73
|
-
cached_templates || NO_TEMPLATES
|
74
|
-
end
|
75
|
-
end
|
101
|
+
@data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield)
|
76
102
|
end
|
77
103
|
|
78
104
|
def cache_query(query) # :nodoc:
|
79
|
-
|
80
|
-
@query_cache[query] ||= canonical_no_templates(yield)
|
81
|
-
else
|
82
|
-
yield
|
83
|
-
end
|
105
|
+
@query_cache[query] ||= canonical_no_templates(yield)
|
84
106
|
end
|
85
107
|
|
86
108
|
def clear
|
@@ -88,7 +110,7 @@ module ActionView
|
|
88
110
|
@query_cache.clear
|
89
111
|
end
|
90
112
|
|
91
|
-
# Get the cache size.
|
113
|
+
# Get the cache size. Do not call this
|
92
114
|
# method. This method is not guaranteed to be here ever.
|
93
115
|
def size # :nodoc:
|
94
116
|
size = 0
|
@@ -106,27 +128,12 @@ module ActionView
|
|
106
128
|
end
|
107
129
|
|
108
130
|
private
|
109
|
-
|
110
131
|
def canonical_no_templates(templates)
|
111
132
|
templates.empty? ? NO_TEMPLATES : templates
|
112
133
|
end
|
113
|
-
|
114
|
-
def templates_have_changed?(cached_templates, fresh_templates)
|
115
|
-
# if either the old or new template list is empty, we don't need to (and can't)
|
116
|
-
# compare modification times, and instead just check whether the lists are different
|
117
|
-
if cached_templates.blank? || fresh_templates.blank?
|
118
|
-
return fresh_templates.blank? != cached_templates.blank?
|
119
|
-
end
|
120
|
-
|
121
|
-
cached_templates_max_updated_at = cached_templates.map(&:updated_at).max
|
122
|
-
|
123
|
-
# if a template has changed, it will be now be newer than all the cached templates
|
124
|
-
fresh_templates.any? { |t| t.updated_at > cached_templates_max_updated_at }
|
125
|
-
end
|
126
134
|
end
|
127
135
|
|
128
|
-
cattr_accessor :caching
|
129
|
-
self.caching = true
|
136
|
+
cattr_accessor :caching, default: true
|
130
137
|
|
131
138
|
class << self
|
132
139
|
alias :caching? :caching
|
@@ -142,14 +149,10 @@ module ActionView
|
|
142
149
|
|
143
150
|
# Normalizes the arguments and passes it on to find_templates.
|
144
151
|
def find_all(name, prefix = nil, partial = false, details = {}, key = nil, locals = [])
|
145
|
-
|
146
|
-
find_templates(name, prefix, partial, details)
|
147
|
-
end
|
148
|
-
end
|
152
|
+
locals = locals.map(&:to_s).sort!.freeze
|
149
153
|
|
150
|
-
def find_all_anywhere(name, prefix, partial = false, details = {}, key = nil, locals = [])
|
151
154
|
cached(key, [name, prefix, partial], details, locals) do
|
152
|
-
|
155
|
+
_find_all(name, prefix, partial, details, key, locals)
|
153
156
|
end
|
154
157
|
end
|
155
158
|
|
@@ -158,19 +161,17 @@ module ActionView
|
|
158
161
|
end
|
159
162
|
|
160
163
|
private
|
164
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
165
|
+
find_templates(name, prefix, partial, details, locals)
|
166
|
+
end
|
161
167
|
|
162
168
|
delegate :caching?, to: :class
|
163
169
|
|
164
170
|
# This is what child classes implement. No defaults are needed
|
165
171
|
# because Resolver guarantees that the arguments are present and
|
166
172
|
# normalized.
|
167
|
-
def find_templates(name, prefix, partial, details,
|
168
|
-
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details,
|
169
|
-
end
|
170
|
-
|
171
|
-
# Helpers that builds a path. Useful for building virtual paths.
|
172
|
-
def build_path(name, prefix, partial)
|
173
|
-
Path.build(name, prefix, partial)
|
173
|
+
def find_templates(name, prefix, partial, details, locals = [])
|
174
|
+
raise NotImplementedError, "Subclasses must implement a find_templates(name, prefix, partial, details, locals = []) method"
|
174
175
|
end
|
175
176
|
|
176
177
|
# Handles templates caching. If a key is given and caching is on
|
@@ -179,25 +180,13 @@ module ActionView
|
|
179
180
|
# resolver is fresher before returning it.
|
180
181
|
def cached(key, path_info, details, locals)
|
181
182
|
name, prefix, partial = path_info
|
182
|
-
locals = locals.map(&:to_s).sort!
|
183
183
|
|
184
184
|
if key
|
185
185
|
@cache.cache(key, name, prefix, partial, locals) do
|
186
|
-
|
186
|
+
yield
|
187
187
|
end
|
188
188
|
else
|
189
|
-
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
# Ensures all the resolver information is set in the template.
|
194
|
-
def decorate(templates, path_info, details, locals)
|
195
|
-
cached = nil
|
196
|
-
templates.each do |t|
|
197
|
-
t.locals = locals
|
198
|
-
t.formats = details[:formats] || [:html] if t.formats.empty?
|
199
|
-
t.variants = details[:variants] || [] if t.variants.empty?
|
200
|
-
t.virtual_path ||= (cached ||= build_path(*path_info))
|
189
|
+
yield
|
201
190
|
end
|
202
191
|
end
|
203
192
|
end
|
@@ -207,41 +196,74 @@ module ActionView
|
|
207
196
|
EXTENSIONS = { locale: ".", formats: ".", variants: "+", handlers: "." }
|
208
197
|
DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}"
|
209
198
|
|
210
|
-
def initialize
|
211
|
-
@pattern =
|
212
|
-
|
199
|
+
def initialize
|
200
|
+
@pattern = DEFAULT_PATTERN
|
201
|
+
@unbound_templates = Concurrent::Map.new
|
202
|
+
@path_parser = PathParser.new
|
203
|
+
super
|
213
204
|
end
|
214
205
|
|
215
|
-
|
206
|
+
def clear_cache
|
207
|
+
@unbound_templates.clear
|
208
|
+
@path_parser = PathParser.new
|
209
|
+
super
|
210
|
+
end
|
216
211
|
|
217
|
-
|
212
|
+
private
|
213
|
+
def _find_all(name, prefix, partial, details, key, locals)
|
218
214
|
path = Path.build(name, prefix, partial)
|
219
|
-
query(path, details, details[:formats],
|
215
|
+
query(path, details, details[:formats], locals, cache: !!key)
|
220
216
|
end
|
221
217
|
|
222
|
-
def query(path, details, formats,
|
223
|
-
|
224
|
-
|
225
|
-
template_paths = find_template_paths(query)
|
226
|
-
template_paths = reject_files_external_to_app(template_paths) unless outside_app_allowed
|
218
|
+
def query(path, details, formats, locals, cache:)
|
219
|
+
template_paths = find_template_paths_from_details(path, details)
|
220
|
+
template_paths = reject_files_external_to_app(template_paths)
|
227
221
|
|
228
222
|
template_paths.map do |template|
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
223
|
+
unbound_template =
|
224
|
+
if cache
|
225
|
+
@unbound_templates.compute_if_absent([template, path.virtual]) do
|
226
|
+
build_unbound_template(template, path.virtual)
|
227
|
+
end
|
228
|
+
else
|
229
|
+
build_unbound_template(template, path.virtual)
|
230
|
+
end
|
231
|
+
|
232
|
+
unbound_template.bind_locals(locals)
|
238
233
|
end
|
239
234
|
end
|
240
235
|
|
236
|
+
def source_for_template(template)
|
237
|
+
Template::Sources::File.new(template)
|
238
|
+
end
|
239
|
+
|
240
|
+
def build_unbound_template(template, virtual_path)
|
241
|
+
handler, format, variant = extract_handler_and_format_and_variant(template)
|
242
|
+
source = source_for_template(template)
|
243
|
+
|
244
|
+
UnboundTemplate.new(
|
245
|
+
source,
|
246
|
+
template,
|
247
|
+
handler,
|
248
|
+
virtual_path: virtual_path,
|
249
|
+
format: format,
|
250
|
+
variant: variant,
|
251
|
+
)
|
252
|
+
end
|
253
|
+
|
241
254
|
def reject_files_external_to_app(files)
|
242
255
|
files.reject { |filename| !inside_path?(@path, filename) }
|
243
256
|
end
|
244
257
|
|
258
|
+
def find_template_paths_from_details(path, details)
|
259
|
+
if path.name.include?(".")
|
260
|
+
ActiveSupport::Deprecation.warn("Rendering actions with '.' in the name is deprecated: #{path}")
|
261
|
+
end
|
262
|
+
|
263
|
+
query = build_query(path, details)
|
264
|
+
find_template_paths(query)
|
265
|
+
end
|
266
|
+
|
245
267
|
def find_template_paths(query)
|
246
268
|
Dir[query].uniq.reject do |filename|
|
247
269
|
File.directory?(filename) ||
|
@@ -264,7 +286,7 @@ module ActionView
|
|
264
286
|
query.gsub!(/:prefix(\/)?/, prefix)
|
265
287
|
|
266
288
|
partial = escape_entry(path.partial? ? "_#{path.name}" : path.name)
|
267
|
-
query.gsub!(
|
289
|
+
query.gsub!(":action", partial)
|
268
290
|
|
269
291
|
details.each do |ext, candidates|
|
270
292
|
if ext == :variants && candidates == :any
|
@@ -278,73 +300,31 @@ module ActionView
|
|
278
300
|
end
|
279
301
|
|
280
302
|
def escape_entry(entry)
|
281
|
-
entry.gsub(/[*?{}\[\]]/, '\\\\\\&'
|
282
|
-
end
|
283
|
-
|
284
|
-
# Returns the file mtime from the filesystem.
|
285
|
-
def mtime(p)
|
286
|
-
File.mtime(p)
|
303
|
+
entry.gsub(/[*?{}\[\]]/, '\\\\\\&')
|
287
304
|
end
|
288
305
|
|
289
306
|
# Extract handler, formats and variant from path. If a format cannot be found neither
|
290
307
|
# from the path, or the handler, we should return the array of formats given
|
291
308
|
# to the resolver.
|
292
309
|
def extract_handler_and_format_and_variant(path)
|
293
|
-
|
294
|
-
pieces.shift
|
295
|
-
|
296
|
-
extension = pieces.pop
|
310
|
+
details = @path_parser.parse(path)
|
297
311
|
|
298
|
-
handler = Template.handler_for_extension(
|
299
|
-
format
|
300
|
-
|
312
|
+
handler = Template.handler_for_extension(details[:handler])
|
313
|
+
format = details[:format] || handler.try(:default_format)
|
314
|
+
variant = details[:variant]
|
301
315
|
|
316
|
+
# Template::Types[format] and handler.default_format can return nil
|
302
317
|
[handler, format, variant]
|
303
318
|
end
|
304
319
|
end
|
305
320
|
|
306
|
-
# A resolver that loads files from the filesystem.
|
307
|
-
# resolving pattern. Such pattern can be a glob string supported by some variables.
|
308
|
-
#
|
309
|
-
# ==== Examples
|
310
|
-
#
|
311
|
-
# Default pattern, loads views the same way as previous versions of rails, eg. when you're
|
312
|
-
# looking for `users/new` it will produce query glob: `users/new{.{en},}{.{html,js},}{.{erb,haml},}`
|
313
|
-
#
|
314
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
315
|
-
#
|
316
|
-
# This one allows you to keep files with different formats in separate subdirectories,
|
317
|
-
# eg. `users/new.html` will be loaded from `users/html/new.erb` or `users/new.html.erb`,
|
318
|
-
# `users/new.js` from `users/js/new.erb` or `users/new.js.erb`, etc.
|
319
|
-
#
|
320
|
-
# FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}")
|
321
|
-
#
|
322
|
-
# If you don't specify a pattern then the default will be used.
|
323
|
-
#
|
324
|
-
# In order to use any of the customized resolvers above in a Rails application, you just need
|
325
|
-
# to configure ActionController::Base.view_paths in an initializer, for example:
|
326
|
-
#
|
327
|
-
# ActionController::Base.view_paths = FileSystemResolver.new(
|
328
|
-
# Rails.root.join("app/views"),
|
329
|
-
# ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}",
|
330
|
-
# )
|
331
|
-
#
|
332
|
-
# ==== Pattern format and variables
|
333
|
-
#
|
334
|
-
# Pattern has to be a valid glob string, and it allows you to use the
|
335
|
-
# following variables:
|
336
|
-
#
|
337
|
-
# * <tt>:prefix</tt> - usually the controller path
|
338
|
-
# * <tt>:action</tt> - name of the action
|
339
|
-
# * <tt>:locale</tt> - possible locale versions
|
340
|
-
# * <tt>:formats</tt> - possible request formats (for example html, json, xml...)
|
341
|
-
# * <tt>:variants</tt> - possible request variants (for example phone, tablet...)
|
342
|
-
# * <tt>:handlers</tt> - possible handlers (for example erb, haml, builder...)
|
343
|
-
#
|
321
|
+
# A resolver that loads files from the filesystem.
|
344
322
|
class FileSystemResolver < PathResolver
|
345
|
-
|
323
|
+
attr_reader :path
|
324
|
+
|
325
|
+
def initialize(path)
|
346
326
|
raise ArgumentError, "path already is a Resolver class" if path.is_a?(Resolver)
|
347
|
-
super(
|
327
|
+
super()
|
348
328
|
@path = File.expand_path(path)
|
349
329
|
end
|
350
330
|
|
@@ -361,30 +341,92 @@ module ActionView
|
|
361
341
|
|
362
342
|
# An Optimized resolver for Rails' most common case.
|
363
343
|
class OptimizedFileSystemResolver < FileSystemResolver #:nodoc:
|
364
|
-
def
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
344
|
+
def initialize(path)
|
345
|
+
super(path)
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
def find_candidate_template_paths(path)
|
350
|
+
# Instead of checking for every possible path, as our other globs would
|
351
|
+
# do, scan the directory for files with the right prefix.
|
352
|
+
query = "#{escape_entry(File.join(@path, path))}*"
|
353
|
+
|
354
|
+
Dir[query].reject do |filename|
|
355
|
+
File.directory?(filename)
|
372
356
|
end
|
373
|
-
end
|
357
|
+
end
|
374
358
|
|
375
|
-
|
376
|
-
|
359
|
+
def find_template_paths_from_details(path, details)
|
360
|
+
if path.name.include?(".")
|
361
|
+
# Fall back to the unoptimized resolver, which will warn
|
362
|
+
return super
|
363
|
+
end
|
364
|
+
|
365
|
+
candidates = find_candidate_template_paths(path)
|
366
|
+
|
367
|
+
regex = build_regex(path, details)
|
368
|
+
|
369
|
+
candidates.uniq.reject do |filename|
|
370
|
+
# This regex match does double duty of finding only files which match
|
371
|
+
# details (instead of just matching the prefix) and also filtering for
|
372
|
+
# case-insensitive file systems.
|
373
|
+
!regex.match?(filename) ||
|
374
|
+
File.directory?(filename)
|
375
|
+
end.sort_by do |filename|
|
376
|
+
# Because we scanned the directory, instead of checking for files
|
377
|
+
# one-by-one, they will be returned in an arbitrary order.
|
378
|
+
# We can use the matches found by the regex and sort by their index in
|
379
|
+
# details.
|
380
|
+
match = filename.match(regex)
|
381
|
+
EXTENSIONS.keys.map do |ext|
|
382
|
+
if ext == :variants && details[ext] == :any
|
383
|
+
match[ext].nil? ? 0 : 1
|
384
|
+
elsif match[ext].nil?
|
385
|
+
# No match should be last
|
386
|
+
details[ext].length
|
387
|
+
else
|
388
|
+
found = match[ext].to_sym
|
389
|
+
details[ext].index(found)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
def build_regex(path, details)
|
396
|
+
query = Regexp.escape(File.join(@path, path))
|
397
|
+
exts = EXTENSIONS.map do |ext, prefix|
|
398
|
+
match =
|
399
|
+
if ext == :variants && details[ext] == :any
|
400
|
+
".*?"
|
401
|
+
else
|
402
|
+
arr = details[ext].compact
|
403
|
+
arr.uniq!
|
404
|
+
arr.map! { |e| Regexp.escape(e) }
|
405
|
+
arr.join("|")
|
406
|
+
end
|
407
|
+
prefix = Regexp.escape(prefix)
|
408
|
+
"(#{prefix}(?<#{ext}>#{match}))?"
|
409
|
+
end.join
|
410
|
+
|
411
|
+
%r{\A#{query}#{exts}\z}
|
412
|
+
end
|
377
413
|
end
|
378
414
|
|
379
415
|
# The same as FileSystemResolver but does not allow templates to store
|
380
416
|
# a virtual path since it is invalid for such resolvers.
|
381
417
|
class FallbackFileSystemResolver < FileSystemResolver #:nodoc:
|
418
|
+
private_class_method :new
|
419
|
+
|
382
420
|
def self.instances
|
383
421
|
[new(""), new("/")]
|
384
422
|
end
|
385
423
|
|
386
|
-
def
|
387
|
-
super
|
424
|
+
def build_unbound_template(template, _)
|
425
|
+
super(template, nil)
|
426
|
+
end
|
427
|
+
|
428
|
+
def reject_files_external_to_app(files)
|
429
|
+
files
|
388
430
|
end
|
389
431
|
end
|
390
432
|
end
|