Skip to content

Commit

Permalink
Replaced TemplateFinder abstraction with ViewLoadPaths
Browse files Browse the repository at this point in the history
  • Loading branch information
josh committed Jun 18, 2008
1 parent 6ffe321 commit bec4b69
Show file tree
Hide file tree
Showing 19 changed files with 494 additions and 352 deletions.
5 changes: 2 additions & 3 deletions actionmailer/lib/action_mailer/base.rb
Expand Up @@ -426,8 +426,7 @@ def register_template_extension(extension)
end

def template_root=(root)
write_inheritable_attribute(:template_root, root)
ActionView::TemplateFinder.process_view_paths(root)
write_inheritable_attribute(:template_root, ActionView::ViewLoadPaths.new(Array(root)))
end
end

Expand Down Expand Up @@ -547,7 +546,7 @@ def template_path
end

def initialize_template_class(assigns)
ActionView::Base.new([template_root], assigns, self)
ActionView::Base.new(template_root, assigns, self)
end

def sort_parts(parts, order = [])
Expand Down
6 changes: 3 additions & 3 deletions actionmailer/test/mail_service_test.rb
Expand Up @@ -942,13 +942,13 @@ def test_return_path_with_deliver
class InheritableTemplateRootTest < Test::Unit::TestCase
def test_attr
expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
assert_equal expected, FunkyPathMailer.template_root
assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)

sub = Class.new(FunkyPathMailer)
sub.template_root = 'test/path'

assert_equal 'test/path', sub.template_root
assert_equal expected, FunkyPathMailer.template_root
assert_equal ['test/path'], sub.template_root.map(&:to_s)
assert_equal [expected], FunkyPathMailer.template_root.map(&:to_s)
end
end

Expand Down
2 changes: 2 additions & 0 deletions actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Edge*

* Replaced TemplateFinder abstraction with ViewLoadPaths [Josh Peek]

* Added block-call style to link_to [Sam Stephenson/DHH]. Example:

<% link_to(@profile) do %>
Expand Down
24 changes: 10 additions & 14 deletions actionpack/lib/action_controller/base.rb
Expand Up @@ -421,8 +421,7 @@ def view_paths
end

def view_paths=(value)
@view_paths = value
ActionView::TemplateFinder.process_view_paths(value)
@view_paths = ActionView::ViewLoadPaths.new(Array(value)) if value
end

# Adds a view_path to the front of the view_paths array.
Expand All @@ -434,8 +433,7 @@ def view_paths=(value)
#
def prepend_view_path(path)
@view_paths = superclass.view_paths.dup if @view_paths.nil?
view_paths.unshift(*path)
ActionView::TemplateFinder.process_view_paths(path)
@view_paths.unshift(*path)
end

# Adds a view_path to the end of the view_paths array.
Expand All @@ -447,8 +445,7 @@ def prepend_view_path(path)
#
def append_view_path(path)
@view_paths = superclass.view_paths.dup if @view_paths.nil?
view_paths.push(*path)
ActionView::TemplateFinder.process_view_paths(path)
@view_paths.push(*path)
end

# Replace sensitive parameter data from the request log.
Expand Down Expand Up @@ -640,11 +637,11 @@ def session_enabled?

# View load paths for controller.
def view_paths
@template.finder.view_paths
@template.view_paths
end

def view_paths=(value)
@template.finder.view_paths = value # Mutex needed
@template.view_paths = ViewLoadPaths.new(value)
end

# Adds a view_path to the front of the view_paths array.
Expand All @@ -654,7 +651,7 @@ def view_paths=(value)
# self.prepend_view_path(["views/default", "views/custom"])
#
def prepend_view_path(path)
@template.finder.prepend_view_path(path) # Mutex needed
@template.view_paths.unshift(*path)
end

# Adds a view_path to the end of the view_paths array.
Expand All @@ -664,7 +661,7 @@ def prepend_view_path(path)
# self.append_view_path(["views/default", "views/custom"])
#
def append_view_path(path)
@template.finder.append_view_path(path) # Mutex needed
@template.view_paths.push(*path)
end

protected
Expand Down Expand Up @@ -1225,17 +1222,16 @@ def close_session
end

def template_exists?(template_name = default_template_name)
@template.finder.file_exists?(template_name)
@template.file_exists?(template_name)
end

def template_public?(template_name = default_template_name)
@template.file_public?(template_name)
end

def template_exempt_from_layout?(template_name = default_template_name)
extension = @template && @template.finder.pick_template_extension(template_name)
name_with_extension = !template_name.include?('.') && extension ? "#{template_name}.#{extension}" : template_name
@@exempt_from_layout.any? { |ext| name_with_extension =~ ext }
template_name = @template.send(:template_file_from_name, template_name) if @template
@@exempt_from_layout.any? { |ext| template_name.to_s =~ ext }
end

def default_template_name(action_name = self.action_name)
Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/dispatcher.rb
Expand Up @@ -134,7 +134,7 @@ def reload_application
run_callbacks :prepare_dispatch

Routing::Routes.reload
ActionView::TemplateFinder.reload! unless ActionView::Base.cache_template_loading
ActionController::Base.view_paths.reload!
end

# Cleanup the application by clearing out loaded classes so they can
Expand Down
2 changes: 1 addition & 1 deletion actionpack/lib/action_controller/layout.rb
Expand Up @@ -304,7 +304,7 @@ def action_has_layout?
end

def layout_directory?(layout_name)
@template.finder.find_template_extension_from_handler(File.join('layouts', layout_name))
@template.view_paths.find_template_file_for_path("#{File.join('layouts', layout_name)}.#{@template.template_format}.erb") ? true : false
end
end
end
3 changes: 2 additions & 1 deletion actionpack/lib/action_view.rb
Expand Up @@ -22,7 +22,8 @@
#++

require 'action_view/template_handlers'
require 'action_view/template_finder'
require 'action_view/template_file'
require 'action_view/view_load_paths'
require 'action_view/template'
require 'action_view/partial_template'
require 'action_view/inline_template'
Expand Down
105 changes: 73 additions & 32 deletions actionpack/lib/action_view/base.rb
@@ -1,17 +1,17 @@
module ActionView #:nodoc:
class ActionViewError < StandardError #:nodoc:
end

class MissingTemplate < ActionViewError #:nodoc:
end

# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
# (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> (or <tt>.rxml</tt>) extension then Jim Weirich's Builder::XmlMarkup library is used.
# If the template file has a <tt>.rjs</tt> extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.
#
#
# = 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 <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the
# following loop for names:
#
# <b>Names of all the people</b>
Expand Down Expand Up @@ -51,7 +51,7 @@ class MissingTemplate < ActionViewError #:nodoc:
# <title><%= @page_title %></title>
#
# == Passing local variables to sub templates
#
#
# You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values:
#
# <%= render "shared/header", { :headline => "Welcome", :person => person } %>
Expand All @@ -77,8 +77,8 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# == Builder
#
# Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
# Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An XmlMarkup object
# named +xml+ is automatically made available to templates with a <tt>.builder</tt> extension.
#
# Here are some basic examples:
#
Expand All @@ -87,7 +87,7 @@ class MissingTemplate < ActionViewError #:nodoc:
# xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a>
# xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\>
# # NOTE: order of attributes is not specified.
#
#
# Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following:
#
# xml.div {
Expand All @@ -111,15 +111,15 @@ class MissingTemplate < ActionViewError #:nodoc:
# xml.description "Basecamp: Recent items"
# xml.language "en-us"
# xml.ttl "40"
#
#
# for item in @recent_items
# xml.item do
# xml.title(item_title(item))
# xml.description(item_description(item)) if item_description(item)
# xml.pubDate(item_pubDate(item))
# xml.guid(@person.firm.account.url + @recent_items.url(item))
# xml.link(@person.firm.account.url + @recent_items.url(item))
#
#
# xml.tag!("dc:creator", item.author_name) if item_has_creator?(item)
# end
# end
Expand All @@ -130,12 +130,12 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# == JavaScriptGenerator
#
# JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
# render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
# modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
# JavaScriptGenerator templates end in <tt>.rjs</tt>. Unlike conventional templates which are used to
# render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to
# modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax
# and make updates to the page where the request originated from.
#
# An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
#
# An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block.
#
# When an <tt>.rjs</tt> action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example:
#
Expand All @@ -145,15 +145,14 @@ class MissingTemplate < ActionViewError #:nodoc:
#
# page.replace_html 'sidebar', :partial => 'sidebar'
# page.remove "person-#{@person.id}"
# page.visual_effect :highlight, 'user-list'
# page.visual_effect :highlight, 'user-list'
#
# This refreshes the sidebar, removes a person element and highlights the user list.
#
#
# See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details.
class Base
include ERB::Util

attr_reader :finder
attr_accessor :base_path, :assigns, :template_extension, :first_render
attr_accessor :controller

Expand All @@ -170,22 +169,22 @@ class Base
# Specify whether file modification times should be checked to see if a template needs recompilation
@@cache_template_loading = false
cattr_accessor :cache_template_loading

def self.cache_template_extensions=(*args)
ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no affect. " <<
"Please remove it from your config files.", caller)
end

# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
# that alert()s the caught exception (and then re-raises it).
@@debug_rjs = false
cattr_accessor :debug_rjs

attr_internal :request

delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
:flash, :logger, :action_name, :controller_name, :to => :controller

module CompiledTemplates #:nodoc:
# holds compiled template code
end
Expand Down Expand Up @@ -221,11 +220,17 @@ def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)
@assigns = assigns_for_first_render
@assigns_added = nil
@controller = controller
@finder = TemplateFinder.new(self, view_paths)
self.view_paths = view_paths
end

# Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
# it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
attr_reader :view_paths

def view_paths=(paths)
@view_paths = ViewLoadPaths.new(Array(paths))
end

# Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true,
# it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt>
# is made available as local variables.
def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc:
if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
Expand All @@ -240,11 +245,11 @@ def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc
render :partial => 'signup' # no mailer_name necessary
END_ERROR
end

Template.new(self, template_path, use_full_path, local_assigns).render_template
end
# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).

# Renders the template present at <tt>template_path</tt> (relative to the view_paths array).
# The hash in <tt>local_assigns</tt> is made available as local variables.
def render(options = {}, local_assigns = {}, &block) #:nodoc:
if options.is_a?(String)
Expand All @@ -257,7 +262,7 @@ def render(options = {}, local_assigns = {}, &block) #:nodoc:

if partial_layout = options.delete(:layout)
if block_given?
wrap_content_for_layout capture(&block) do
wrap_content_for_layout capture(&block) do
concat(render(options.merge(:partial => partial_layout)))
end
else
Expand Down Expand Up @@ -314,6 +319,10 @@ def template_format
end
end

def file_exists?(template_path)
view_paths.template_exists?(template_file_from_name(template_path))
end

private
def wrap_content_for_layout(content)
original_content_for_layout, @content_for_layout = @content_for_layout, content
Expand All @@ -334,11 +343,43 @@ def evaluate_assigns
def assign_variables_from_controller
@assigns.each { |key, value| instance_variable_set("@#{key}", value) }
end

def execute(template)
send(template.method, template.locals) do |*names|
instance_variable_get "@content_for_#{names.first || 'layout'}"
end
end
end

def template_file_from_name(template_name)
template_name = TemplateFile.from_path(template_name)
pick_template_extension(template_name) unless template_name.extension
end

# Gets the extension for an existing template with the given template_path.
# Returns the format with the extension if that template exists.
#
# pick_template_extension('users/show')
# # => 'html.erb'
#
# pick_template_extension('users/legacy')
# # => "rhtml"
#
def pick_template_extension(file)
if f = self.view_paths.find_template_file_for_path(file.dup_with_extension(template_format)) || file_from_first_render(file)
f
elsif template_format == :js && f = self.view_paths.find_template_file_for_path(file.dup_with_extension(:html))
@template_format = :html
f
else
nil
end
end

# Determine the template extension from the <tt>@first_render</tt> filename
def file_from_first_render(file)
if extension = File.basename(@first_render.to_s)[/^[^.]+\.(.+)$/, 1]
file.dup_with_extension(extension)
end
end
end
end

2 comments on commit bec4b69

@acechase
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pretty significant change. Is there going to be any documentation explaining the differences this change brings about and what its added benefits are? I’ve dug through the code to get a decent handle on it, but not enough to explain it to others :)

The reason I ask is because I know several plugins use alias_method_chain to hook into TemplateFinder and I think a high level description could help those project maintainers more quickly bring their codebase up to speed with these changes.

@josh
Copy link
Contributor Author

@josh josh commented on bec4b69 Oct 15, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be no change to any of the documented methods. None of the public API was changed.

However, if you are monkey patching deep into TemplateFinder, your going need to make some changes.

If this is what you were doing, I would highly suggest you raise a thread in the mailing list to get an “official” api for what you are trying to do so we don’t break it in the future.

Please sign in to comment.