Skip to content

Commit db543ba

Browse files
authored
Merge pull request rails#39361 from jhawthorn/path_parser
Introduce Resolver::PathParser
2 parents 787b991 + a70ad60 commit db543ba

File tree

7 files changed

+158
-16
lines changed

7 files changed

+158
-16
lines changed

actionmailer/lib/action_mailer.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,5 @@ def self.eager_load!
6767
ActiveSupport.on_load(:action_view) do
6868
ActionView::Base.default_formats ||= Mime::SET.symbols
6969
ActionView::Template::Types.delegate_to Mime
70+
ActionView::LookupContext::DetailsKey.clear
7071
end

actionpack/lib/action_dispatch.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,5 @@ module Session
116116
ActiveSupport.on_load(:action_view) do
117117
ActionView::Base.default_formats ||= Mime::SET.symbols
118118
ActionView::Template::Types.delegate_to Mime
119+
ActionView::LookupContext::DetailsKey.clear
119120
end

actionview/lib/action_view/template/resolver.rb

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,44 @@ def to_str
3535
alias :to_s :to_str
3636
end
3737

38+
class PathParser # :nodoc:
39+
def initialize
40+
@regex = build_path_regex
41+
end
42+
43+
def build_path_regex
44+
handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
45+
formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
46+
locales = "[a-z]{2}(?:-[A-Z]{2})?"
47+
variants = "[^.]*"
48+
49+
%r{
50+
\A
51+
(?:(?<prefix>.*)/)?
52+
(?<partial>_)?
53+
(?<action>.*?)
54+
(?:\.(?<locale>#{locales}))??
55+
(?:\.(?<format>#{formats}))??
56+
(?:\+(?<variant>#{variants}))??
57+
(?:\.(?<handler>#{handlers}))?
58+
\z
59+
}x
60+
end
61+
62+
def parse(path)
63+
match = @regex.match(path)
64+
{
65+
prefix: match[:prefix] || "",
66+
action: match[:action],
67+
partial: !!match[:partial],
68+
locale: match[:locale]&.to_sym,
69+
handler: match[:handler]&.to_sym,
70+
format: match[:format]&.to_sym,
71+
variant: match[:variant]
72+
}
73+
end
74+
end
75+
3876
# Threadsafe template cache
3977
class Cache #:nodoc:
4078
class SmallCache < Concurrent::Map
@@ -172,11 +210,13 @@ def initialize(pattern = nil)
172210
@pattern = DEFAULT_PATTERN
173211
end
174212
@unbound_templates = Concurrent::Map.new
213+
@path_parser = PathParser.new
175214
super()
176215
end
177216

178217
def clear_cache
179218
@unbound_templates.clear
219+
@path_parser = PathParser.new
180220
super()
181221
end
182222

@@ -278,18 +318,11 @@ def escape_entry(entry)
278318
# from the path, or the handler, we should return the array of formats given
279319
# to the resolver.
280320
def extract_handler_and_format_and_variant(path)
281-
pieces = File.basename(path).split(".")
282-
pieces.shift
321+
details = @path_parser.parse(path)
283322

284-
extension = pieces.pop
285-
286-
handler = Template.handler_for_extension(extension)
287-
format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
288-
format = if format
289-
Template::Types[format]&.ref
290-
elsif handler.respond_to?(:default_format) # default_format can return nil
291-
handler.default_format
292-
end
323+
handler = Template.handler_for_extension(details[:handler])
324+
format = details[:format] || handler.try(:default_format)
325+
variant = details[:variant]
293326

294327
# Template::Types[format] and handler.default_format can return nil
295328
[handler, format, variant]

actionview/test/actionpack/controller/layout_test.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@ def self._implied_layout_name; to_s.underscore.gsub(/_controller$/, "") ; end
1818
module TemplateHandlerHelper
1919
def with_template_handler(*extensions, handler)
2020
ActionView::Template.register_template_handler(*extensions, handler)
21+
ActionController::Base.view_paths.paths.each(&:clear_cache)
22+
ActionView::LookupContext::DetailsKey.clear
2123
yield
2224
ensure
2325
ActionView::Template.unregister_template_handler(*extensions)
26+
ActionController::Base.view_paths.paths.each(&:clear_cache)
27+
ActionView::LookupContext::DetailsKey.clear
2428
end
2529
end
2630

actionview/test/template/resolver_shared_tests.rb

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -148,22 +148,83 @@ def test_templates_with_optional_locale_shares_common_object
148148

149149
def test_templates_sort_by_formats_json_first
150150
with_file "test/hello_world.html.erb", "Hello HTML!"
151-
with_file "test/hello_world.json.jbuilder", "Hello JSON!"
151+
with_file "test/hello_world.json.builder", "Hello JSON!"
152152

153-
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :jbuilder])
153+
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :builder])
154154

155155
assert_equal 2, templates.size
156156
assert_equal "Hello JSON!", templates[0].source
157+
assert_equal :json, templates[0].format
158+
assert_equal "Hello HTML!", templates[1].source
159+
assert_equal :html, templates[1].format
157160
end
158161

159162
def test_templates_sort_by_formats_html_first
160163
with_file "test/hello_world.html.erb", "Hello HTML!"
161-
with_file "test/hello_world.json.jbuilder", "Hello JSON!"
164+
with_file "test/hello_world.json.builder", "Hello JSON!"
162165

163-
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :jbuilder])
166+
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
164167

165168
assert_equal 2, templates.size
166169
assert_equal "Hello HTML!", templates[0].source
170+
assert_equal :html, templates[0].format
171+
assert_equal "Hello JSON!", templates[1].source
172+
assert_equal :json, templates[1].format
173+
end
174+
175+
def test_templates_with_variant
176+
with_file "test/hello_world.html+mobile.erb", "Hello HTML!"
177+
178+
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
179+
180+
assert_equal 1, templates.size
181+
assert_equal "Hello HTML!", templates[0].source
182+
assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler
183+
assert_equal :html, templates[0].format
184+
assert_equal "mobile", templates[0].variant
185+
end
186+
187+
def test_finds_variants_in_order
188+
with_file "test/hello_world.html+tricorder.erb", "Hello Spock!"
189+
with_file "test/hello_world.html+lcars.erb", "Hello Geordi!"
190+
191+
tricorder = context.find("hello_world", "test", false, [], { variants: [:tricorder] })
192+
lcars = context.find("hello_world", "test", false, [], { variants: [:lcars] })
193+
194+
assert_equal "Hello Spock!", tricorder.source
195+
assert_equal "tricorder", tricorder.variant
196+
assert_equal "Hello Geordi!", lcars.source
197+
assert_equal "lcars", lcars.variant
198+
199+
templates = context.find_all("hello_world", "test", false, [], { variants: [:tricorder, :lcars] })
200+
assert_equal [tricorder, lcars], templates
201+
202+
templates = context.find_all("hello_world", "test", false, [], { variants: [:lcars, :tricorder] })
203+
assert_equal [lcars, tricorder], templates
204+
end
205+
206+
def test_templates_no_format_with_variant
207+
with_file "test/hello_world+mobile.erb", "Hello HTML!"
208+
209+
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
210+
211+
assert_equal 1, templates.size
212+
assert_equal "Hello HTML!", templates[0].source
213+
assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler
214+
assert_nil templates[0].format
215+
assert_equal "mobile", templates[0].variant
216+
end
217+
218+
def test_templates_no_format_or_handler_with_variant
219+
with_file "test/hello_world+mobile", "Hello HTML!"
220+
221+
templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
222+
223+
assert_equal 1, templates.size
224+
assert_equal "Hello HTML!", templates[0].source
225+
assert_kind_of ActionView::Template::Handlers::Raw, templates[0].handler
226+
assert_nil templates[0].format
227+
assert_equal "mobile", templates[0].variant
167228
end
168229

169230
def test_virtual_path_is_preserved_with_dot

railties/test/application/per_request_digest_cache_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def index
6262
end
6363

6464
test "template digests are cleared before a request" do
65-
assert_called(ActionView::LookupContext::DetailsKey, :clear) do
65+
assert_called(ActionView::LookupContext::DetailsKey, :clear, times: 3) do
6666
get "/customers"
6767
assert_equal 200, last_response.status
6868
end

railties/test/application/rendering_test.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,47 @@ def show
4242
get "/pages/foo.bar"
4343
assert_equal 200, last_response.status
4444
end
45+
46+
test "New formats and handlers are detected from initializers" do
47+
app_file "config/routes.rb", <<-RUBY
48+
Rails.application.routes.draw do
49+
root to: 'pages#show'
50+
end
51+
RUBY
52+
53+
app_file "app/controllers/pages_controller.rb", <<-RUBY
54+
class PagesController < ApplicationController
55+
layout false
56+
57+
def show
58+
render :show, formats: [:awesome], handlers: [:rubby]
59+
end
60+
end
61+
RUBY
62+
63+
app_file "app/views/pages/show.awesome.rubby", <<-RUBY
64+
{
65+
format: @current_template.format,
66+
handler: @current_template.handler
67+
}.inspect
68+
RUBY
69+
70+
app_file "config/initializers/mime_types.rb", <<-RUBY
71+
Mime::Type.register "text/awesome", :awesome
72+
RUBY
73+
74+
app_file "config/initializers/template_handlers.rb", <<-RUBY
75+
module RubbyHandler
76+
def self.call(_, source)
77+
source
78+
end
79+
end
80+
ActionView::Template.register_template_handler(:rubby, RubbyHandler)
81+
RUBY
82+
83+
get "/"
84+
assert_equal 200, last_response.status
85+
assert_equal "{:format=>:awesome, :handler=>RubbyHandler}", last_response.body
86+
end
4587
end
4688
end

0 commit comments

Comments
 (0)