diff options
Diffstat (limited to 'actionpack/lib')
46 files changed, 649 insertions, 276 deletions
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index d63d17f4c5..d3929b685c 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -32,9 +32,9 @@ module AbstractController # @current_user ||= User.find_by_id(session[:user]) # end # - # def logged_in? - # current_user != nil - # end + # def logged_in? + # current_user != nil + # end # end # # In a view: diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 31df9d605c..8c1f548e47 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -58,7 +58,11 @@ end # All of these simply register additional autoloads require 'action_view' -require 'action_controller/vendor/html-scanner' +require 'action_view/vendor/html-scanner' + +ActiveSupport.on_load(:action_view) do + ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) +end # Common Active Support usage in Action Controller require 'active_support/core_ext/class/attribute_accessors' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 71425cd542..f829f5e8a2 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -88,15 +88,6 @@ module ActionController # # Do not put secret information in cookie-based sessions! # - # Other options for session storage: - # - # * ActiveRecord::SessionStore - Sessions are stored in your database, which works better than PStore with multiple app servers and, - # unlike CookieStore, hides your session contents from the user. To use ActiveRecord::SessionStore, set - # - # MyApplication::Application.config.session_store :active_record_store - # - # in your <tt>config/initializers/session_store.rb</tt> and run <tt>script/rails g session_migration</tt>. - # # == Responses # # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 9118806059..be29099fbe 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -48,11 +48,10 @@ module ActionController #:nodoc: config.cache_store = ActiveSupport::Cache.lookup_store(store) end - private - - def cache_configured? - perform_caching && cache_store - end + private + def cache_configured? + perform_caching && cache_store + end end include RackDelegation @@ -73,14 +72,14 @@ module ActionController #:nodoc: request.get? && response.status == 200 end - protected - # Convenience accessor - def cache(key, options = {}, &block) - if cache_configured? - cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) - else - yield + protected + # Convenience accessor + def cache(key, options = {}, &block) + if cache_configured? + cache_store.fetch(ActiveSupport::Cache.expand_cache_key(key, :controller), options, &block) + else + yield + end end - end end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 2193dde667..7521deaaca 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/class/attribute' + module ActionController module ConditionalGet extend ActiveSupport::Concern @@ -5,6 +7,33 @@ module ActionController include RackDelegation include Head + included do + class_attribute :etaggers + self.etaggers = [] + end + + module ClassMethods + # Allows you to consider additional controller-wide information when generating an etag. + # For example, if you serve pages tailored depending on who's logged in at the moment, you + # may want to add the current user id to be part of the etag to prevent authorized displaying + # of cached pages. + # + # === Example + # + # class InvoicesController < ApplicationController + # etag { current_user.try :id } + # + # def show + # # Etag will differ even for the same invoice when it's viewed by a different current_user + # @invoice = Invoice.find(params[:id]) + # fresh_when(@invoice) + # end + # end + def etag(&etagger) + self.etaggers += [etagger] + end + end + # Sets the etag, last_modified, or both on the response and renders a # <tt>304 Not Modified</tt> response if the request is already fresh. # @@ -42,12 +71,12 @@ module ActionController options.assert_valid_keys(:etag, :last_modified, :public) else record = record_or_options - options = { :etag => record, :last_modified => record.try(:updated_at) }.merge(additional_options) + options = { etag: record, last_modified: record.try(:updated_at) }.merge(additional_options) end - response.etag = options[:etag] if options[:etag] - response.last_modified = options[:last_modified] if options[:last_modified] - response.cache_control[:public] = true if options[:public] + response.etag = combine_etags(options[:etag]) if options[:etag] + response.last_modified = options[:last_modified] if options[:last_modified] + response.cache_control[:public] = true if options[:public] head :not_modified if request.fresh?(response) end @@ -133,5 +162,11 @@ module ActionController def expires_now #:doc: response.cache_control.replace(:no_cache => true) end + + + private + def combine_etags(etag) + [ etag, *etaggers.map { |etagger| instance_exec(&etagger) }.compact ] + end end end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index 8fd8f4797c..3c9d0c86a7 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -16,6 +16,9 @@ module ActionController end end + class ActionController::UrlGenerationError < RoutingError #:nodoc: + end + class MethodNotAllowed < ActionControllerError #:nodoc: def initialize(*allowed_methods) super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index 640ebf5f00..ca4ae532ca 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -11,6 +11,7 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger + include ActionController::RackDelegation attr_internal :view_runtime diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index d3ac406618..bffd2a02d0 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,83 +1,20 @@ -require 'active_support/core_ext/module' -require 'action_controller/model_naming' +require 'active_support/deprecation' +require 'action_view/record_identifier' module ActionController - # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or - # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to - # a higher logical level. - # - # # routes - # resources :posts - # - # # view - # <%= div_for(post) do %> <div id="post_45" class="post"> - # <%= post.body %> What a wonderful world! - # <% end %> </div> - # - # # controller - # def update - # post = Post.find(params[:id]) - # post.update_attributes(params[:post]) - # - # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) - # end - # - # As the example above shows, you can stop caring to a large extent what the actual id of the post is. - # You just know that one is being assigned and that the subsequent calls in redirect_to expect that - # same naming convention and allows you to write less code if you follow it. module RecordIdentifier - extend self + MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' + + 'If you would like to use it in controllers, please include ' + + 'ActionView::RecodIdentifier module.' - include ModelNaming - - JOIN = '_'.freeze - NEW = 'new'.freeze - - # The DOM class convention is to use the singular form of an object or class. - # - # dom_class(post) # => "post" - # dom_class(Person) # => "person" - # - # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: - # - # dom_class(post, :edit) # => "edit_post" - # dom_class(Person, :edit) # => "edit_person" - def dom_class(record_or_class, prefix = nil) - singular = model_name_from_record_or_class(record_or_class).param_key - prefix ? "#{prefix}#{JOIN}#{singular}" : singular - end - - # The DOM id convention is to use the singular form of an object or class with the id following an underscore. - # If no id is found, prefix with "new_" instead. - # - # dom_id(Post.find(45)) # => "post_45" - # dom_id(Post.new) # => "new_post" - # - # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: - # - # dom_id(Post.find(45), :edit) # => "edit_post_45" - # dom_id(Post.new, :custom) # => "custom_post" def dom_id(record, prefix = nil) - if record_id = record_key_for_dom_id(record) - "#{dom_class(record, prefix)}#{JOIN}#{record_id}" - else - dom_class(record, prefix || NEW) - end + ActiveSupport::Deprecation.warn 'dom_id ' + MESSAGE + ActionView::RecordIdentifier.dom_id(record, prefix) end - protected - - # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. - # This can be overwritten to customize the default generated string representation if desired. - # If you need to read back a key from a dom_id in order to query for the underlying database record, - # you should write a helper like 'person_record_from_dom_id' that will extract the key either based - # on the default implementation (which just joins all key attributes with '_') or on your own - # overwritten version of the method. By default, this implementation passes the key string through a - # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to - # make sure yourself that your dom ids are valid, in case you overwrite this method. - def record_key_for_dom_id(record) - key = convert_to_model(record).to_key - key ? key.join('_') : key + def dom_class(record, prefix = nil) + ActiveSupport::Deprecation.warn 'dom_class ' + MESSAGE + ActionView::RecordIdentifier.dom_class(record, prefix) end end end diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb index 879b31e60e..896208bc05 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner.rb @@ -1,20 +1,5 @@ -$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner" +require 'action_view/vendor/html-scanner' +require 'active_support/deprecation' -module HTML - extend ActiveSupport::Autoload - - eager_autoload do - autoload :CDATA, 'html/node' - autoload :Document, 'html/document' - autoload :FullSanitizer, 'html/sanitizer' - autoload :LinkSanitizer, 'html/sanitizer' - autoload :Node, 'html/node' - autoload :Sanitizer, 'html/sanitizer' - autoload :Selector, 'html/selector' - autoload :Tag, 'html/node' - autoload :Text, 'html/node' - autoload :Tokenizer, 'html/tokenizer' - autoload :Version, 'html/version' - autoload :WhiteListSanitizer, 'html/sanitizer' - end -end +ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' + + 'This file will be removed in Rails 4.1' diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 57b4678add..0ec355246e 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -101,3 +101,8 @@ module ActionDispatch end autoload :Mime, 'action_dispatch/http/mime_type' + +ActiveSupport.on_load(:action_view) do + ActionView::Base.default_formats ||= Mime::SET.symbols + ActionView::Template::Types.delegate_to Mime +end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index f64cff8394..b52f66faf1 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -757,7 +757,7 @@ module ActionDispatch # # Routes can also be constrained to an IP or a certain range of IP addresses: # - # constraints(:ip => /192.168.\d+.\d+/) do + # constraints(:ip => /192\.168\.\d+\.\d+/) do # resources :posts # end # diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 32d267d1d6..1fe40d8458 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -438,12 +438,11 @@ module ActionDispatch attr_reader :options, :recall, :set, :named_route - def initialize(options, recall, set, extras = false) + def initialize(options, recall, set) @named_route = options.delete(:use_route) @options = options.dup @recall = recall.dup @set = set - @extras = extras normalize_options! normalize_controller_action_id! @@ -526,20 +525,12 @@ module ActionDispatch recall[:action] = options.delete(:action) if options[:action] == 'index' end + # Generates a path from routes, returns [path, params] + # if no path is returned the formatter will raise Journey::Router::RoutingError def generate - path, params = @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE) - - raise_routing_error unless path - - return [path, params.keys] if @extras - - [path, params] - rescue Journey::Router::RoutingError - raise_routing_error - end - - def raise_routing_error - raise ActionController::RoutingError, "No route matches #{options.inspect}" + @set.formatter.generate(:path_info, named_route, options, recall, PARAMETERIZE) + rescue Journey::Router::RoutingError => e + raise ActionController::UrlGenerationError, "No route matches #{options.inspect} #{e.message}" end def different_controller? @@ -564,11 +555,12 @@ module ActionDispatch end def generate_extras(options, recall={}) - generate(options, recall, true) + path, params = generate(options, recall) + return path, params.keys end - def generate(options, recall = {}, extras = false) - Generator.new(options, recall, self, extras).generate + def generate(options, recall = {}) + Generator.new(options, recall, self).generate end RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index f4c708ea33..d4cd537048 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -95,6 +95,8 @@ module ActionDispatch self.default_url_options = {} end + + include(*_url_for_modules) if respond_to?(:_url_for_modules) end def initialize(*) diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb index 7dc3d0f97c..6c61d4e61a 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb @@ -1,4 +1,4 @@ -require 'action_controller/vendor/html-scanner' +require 'action_view/vendor/html-scanner' module ActionDispatch module Assertions diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index d19d116a1f..9388d44eef 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -1,4 +1,5 @@ -require 'action_controller/vendor/html-scanner' +require 'action_view/vendor/html-scanner' +require 'active_support/core_ext/object/inclusion' #-- # Copyright (c) 2006 Assaf Arkin (http://labnotes.org) diff --git a/actionpack/lib/action_dispatch/testing/assertions/tag.rb b/actionpack/lib/action_dispatch/testing/assertions/tag.rb index 68f1347e7c..2e38266aba 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/tag.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/tag.rb @@ -1,4 +1,4 @@ -require 'action_controller/vendor/html-scanner' +require 'action_view/vendor/html-scanner' module ActionDispatch module Assertions diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 9d11c284f5..091b0d8cd2 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -33,9 +33,12 @@ module ActionView autoload :Base autoload :Context autoload :CompiledTemplates, "action_view/context" + autoload :Digestor autoload :Helpers autoload :LookupContext autoload :PathSet + autoload :RecordIdentifier + autoload :RoutingUrlFor autoload :Template autoload_under "renderer" do @@ -68,6 +71,7 @@ module ActionView autoload :MissingTemplate autoload :ActionViewError autoload :EncodingError + autoload :MissingRequestError autoload :TemplateError autoload :WrongEncodingError end diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index 81880d17ea..4bbb31b3ee 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -1,6 +1,5 @@ require 'zlib' require 'active_support/core_ext/file' -require 'action_controller/metal/exceptions' module ActionView class AssetPaths #:nodoc: @@ -98,7 +97,7 @@ module ActionView end def invalid_asset_host!(help_message) - raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. #{help_message}" + raise ActionView::MissingRequestError, "This asset host cannot be computed without a request in scope. #{help_message}" end # Pick an asset host for this source. Returns +nil+ if no host is set, diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 749332eca7..3464ec523e 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -146,6 +146,9 @@ module ActionView #:nodoc: cattr_accessor :prefix_partial_path_with_controller_namespace @@prefix_partial_path_with_controller_namespace = true + # Specify default_formats that can be rendered. + cattr_accessor :default_formats + class_attribute :_routes class_attribute :logger @@ -178,8 +181,6 @@ module ActionView #:nodoc: def initialize(context = nil, assigns = {}, controller = nil, formats = nil) #:nodoc: @_config = ActiveSupport::InheritableOptions.new - # Handle all these for backwards compatibility. - # TODO Provide a new API for AV::Base and deprecate this one. if context.is_a?(ActionView::Renderer) @view_renderer = context else diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb new file mode 100644 index 0000000000..899100d06c --- /dev/null +++ b/actionpack/lib/action_view/digestor.rb @@ -0,0 +1,104 @@ +require 'active_support/core_ext' +require 'logger' + +module ActionView + class Digestor + EXPLICIT_DEPENDENCY = /# Template Dependency: ([^ ]+)/ + + # Matches: + # render partial: "comments/comment", collection: commentable.comments + # render "comments/comments" + # render 'comments/comments' + # render('comments/comments') + # + # render(@topic) => render("topics/topic") + # render(topics) => render("topics/topic") + # render(message.topics) => render("topics/topic") + RENDER_DEPENDENCY = / + render\s* # render, followed by optional whitespace + \(? # start an optional parenthesis for the render call + (partial:|:partial\s+=>)?\s* # naming the partial, used with collection -- 1st capture + ([@a-z"'][@a-z_\/\."']+) # the template name itself -- 2nd capture + /x + + cattr_accessor(:cache) { Hash.new } + cattr_accessor(:logger, instance_reader: true) { ActionView::Base.logger } + + def self.digest(name, format, finder, options = {}) + cache["#{name}.#{format}"] ||= new(name, format, finder, options).digest + end + + attr_reader :name, :format, :finder, :options + + def initialize(name, format, finder, options = {}) + @name, @format, @finder, @options = name, format, finder, options + end + + def digest + Digest::MD5.hexdigest("#{name}.#{format}-#{source}-#{dependency_digest}").tap do |digest| + logger.try :info, "Cache digest for #{name}.#{format}: #{digest}" + end + rescue ActionView::MissingTemplate + logger.try :error, "Couldn't find template for digesting: #{name}.#{format}" + '' + end + + def dependencies + render_dependencies + explicit_dependencies + rescue ActionView::MissingTemplate + [] # File doesn't exist, so no dependencies + end + + def nested_dependencies + dependencies.collect do |dependency| + dependencies = Digestor.new(dependency, format, finder, partial: true).nested_dependencies + dependencies.any? ? { dependency => dependencies } : dependency + end + end + + + private + def logical_name + name.gsub(%r|/_|, "/") + end + + def directory + name.split("/").first + end + + def partial? + options[:partial] || name.include?("/_") + end + + def source + @source ||= finder.find(logical_name, [], partial?, formats: [ format ]).source + end + + + def dependency_digest + dependencies.collect do |template_name| + Digestor.digest(template_name, format, finder, partial: true) + end.join("-") + end + + def render_dependencies + source.scan(RENDER_DEPENDENCY). + collect(&:second).uniq. + + # render(@topic) => render("topics/topic") + # render(topics) => render("topics/topic") + # render(message.topics) => render("topics/topic") + collect { |name| name.sub(/\A@?([a-z]+\.)*([a-z_]+)\z/) { "#{$2.pluralize}/#{$2.singularize}" } }. + + # render("headline") => render("message/headline") + collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. + + # replace quotes from string renders + collect { |name| name.gsub(/["']/, "") } + end + + def explicit_dependencies + source.scan(EXPLICIT_DEPENDENCY).flatten.uniq + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index ceb56824fa..db4da6f9c8 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -222,6 +222,14 @@ module ActionView # auto_discovery_link_tag(:rss, "http://www.example.com/feed.rss", {:title => "Example RSS"}) # # => <link rel="alternate" type="application/rss+xml" title="Example RSS" href="http://www.example.com/feed" /> def auto_discovery_link_tag(type = :rss, url_options = {}, tag_options = {}) + if !(type == :rss || type == :atom) && tag_options[:type].blank? + message = "You have passed type other than :rss or :atom to auto_discovery_link_tag and haven't supplied " + + "the :type option key. This behavior is deprecated and will be remove in Rails 4.1. You should pass " + + ":type option explicitly if you want to use other types, for example: " + + "auto_discovery_link_tag(:xml, '/feed.xml', :type => 'application/xml')" + ActiveSupport::Deprecation.warn message + end + tag( "link", "rel" => tag_options[:rel] || "alternate", diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 39518268df..59e1015976 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -13,10 +13,9 @@ module ActionView # kick out old entries. For more on key-based expiration, see: # http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works # - # When using this method, you list the cache dependencies as part of - # the name of the cache, like so: + # When using this method, you list the cache dependency as the name of the cache, like so: # - # <% cache [ "v1", project ] do %> + # <% cache project do %> # <b>All the topics on this project</b> # <%= render project.topics %> # <% end %> @@ -24,15 +23,89 @@ module ActionView # This approach will assume that when a new topic is added, you'll touch # the project. The cache key generated from this call will be something like: # - # views/v1/projects/123-20120806214154 - # ^class ^id ^updated_at + # views/projects/123-20120806214154/7a1156131a6928cb0026877f8b749ac9 + # ^class ^id ^updated_at ^template tree digest # - # If you update the rendering of topics, you just bump the version to v2. - # Otherwise the cache is automatically bumped whenever the project updated_at - # is touched. + # The cache is thus automatically bumped whenever the project updated_at is touched. + # + # If your template cache depends on multiple sources (try to avoid this to keep things simple), + # you can name all these dependencies as part of an array: + # + # <% cache [ project, current_user ] do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> + # <% end %> + # + # This will include both records as part of the cache key and updating either of them will + # expire the cache. + # + # ==== Template digest + # + # 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 + # cache do/end call. So it's possible that changing something outside of that call will + # still expire the cache. + # + # Additionally, the digestor will automatically look through your template file for + # explicit and implicit dependencies, and include those as part of the digest. + # + # ==== Implicit dependencies + # + # Most template dependencies can be derived from calls to render in the template itself. + # Here are some examples of render calls that Cache Digests knows how to decode: + # + # render partial: "comments/comment", collection: commentable.comments + # render "comments/comments" + # render 'comments/comments' + # render('comments/comments') + # + # render "header" => render("comments/header") + # + # render(@topic) => render("topics/topic") + # render(topics) => render("topics/topic") + # render(message.topics) => 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: + # + # render group_of_attachments + # render @project.documents.where(published: true).order('created_at') + # + # You will have to rewrite those to the explicit form: + # + # render partial: 'attachments/attachment', collection: group_of_attachments + # render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at') + # + # === Explicit dependencies + # + # Some times you'll have template dependencies that can't be derived at all. This is typically + # the case when you have template rendering that happens in helpers. Here's an example: + # + # <%= render_sortable_todolists @project.todolists %> + # + # You'll need to use a special comment format to call those out: + # + # <%# Template Dependency: todolists/todolist %> + # <%= render_sortable_todolists @project.todolists %> + # + # The pattern used to match these is /# Template Dependency: ([^ ]+)/, so it's important that you type it out just so. + # You can only declare one template dependency per line. + # + # === External dependencies + # + # If you use a helper method, for example, inside of 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 + # must change. One recommendation is to simply be explicit in a comment, like: + # + # <%# Helper Dependency Updated: May 6, 2012 at 6pm %> + # <%= some_helper_method(person) %> + # + # Now all you'll have to do is change that timestamp when the helper method changes. def cache(name = {}, options = nil, &block) if controller.perform_caching - safe_concat(fragment_for(name, options, &block)) + safe_concat(fragment_for(fragment_name_with_digest(name), options, &block)) else yield end @@ -58,6 +131,17 @@ module ActionView controller.write_fragment(name, fragment, options) end end + + def fragment_name_with_digest(name) + if @virtual_path + [ + *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), + Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context) + ] + else + name + end + end end end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index dea2aa69dd..795440cacc 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -140,12 +140,19 @@ module ActionView # Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>. # # time_ago_in_words(3.minutes.from_now) # => 3 minutes + # time_ago_in_words(3.minutes.ago) # => 3 minutes # time_ago_in_words(Time.now - 15.hours) # => about 15 hours # time_ago_in_words(Time.now) # => less than a minute # time_ago_in_words(Time.now, :include_seconds => true) # => less than 5 seconds # # from_time = Time.now - 3.days - 14.minutes - 25.seconds # time_ago_in_words(from_time) # => 3 days + # + # from_time = (3.days + 14.minutes + 25.seconds).ago + # time_ago_in_words(from_time) # => 3 days + # + # Note that you cannot pass a <tt>Numeric</tt> value to <tt>time_ago_in_words</tt>. + # def time_ago_in_words(from_time, include_seconds_or_options = {}) distance_of_time_in_words(from_time, Time.now, include_seconds_or_options) end @@ -956,12 +963,15 @@ module ActionView # build_hidden(:year, 2008) # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />" def build_hidden(type, value) - (tag(:input, { + select_options = { :type => "hidden", :id => input_id_from_type(type), :name => input_name_from_type(type), :value => value - }.merge(@html_options.slice(:disabled))) + "\n").html_safe + }.merge(@html_options.slice(:disabled)) + select_options.merge!(:disabled => 'disabled') if @options[:disabled] + + tag(:input, select_options) + "\n".html_safe end # Returns the name attribute for the input tag. diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index b79577bcd3..17fe7dc1b4 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -4,12 +4,12 @@ require 'action_view/helpers/tag_helper' require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' require 'action_view/helpers/tags' +require 'action_view/model_naming' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/string/inflections' -require 'action_controller/model_naming' module ActionView # = Action View Form Helpers @@ -115,7 +115,7 @@ module ActionView include FormTagHelper include UrlHelper - include ActionController::ModelNaming + include ModelNaming # Creates a form that allows the user to create or update the attributes # of a specific model object. @@ -1156,7 +1156,7 @@ module ActionView end class FormBuilder - include ActionController::ModelNaming + include ModelNaming # The methods which wrap a form helper call. class_attribute :field_helpers diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 9b35f076e5..dded9aab7c 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -1,10 +1,8 @@ -require 'action_controller/record_identifier' - module ActionView # = Action View Record Tag Helpers module Helpers module RecordTagHelper - include ActionController::RecordIdentifier + include ActionView::RecordIdentifier # Produces a wrapper DIV element with id and class parameters that # relate to the specified Active Record object. Usage example: diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index aaf0e0344a..9c76c26ace 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/object/try' -require 'action_controller/vendor/html-scanner' +require 'action_view/vendor/html-scanner' module ActionView # = Action View Sanitize Helpers diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index fe3240fdc1..3f65791aa0 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -2,7 +2,6 @@ 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_dispatch' module ActionView # = Action View URL Helpers @@ -20,115 +19,34 @@ module ActionView extend ActiveSupport::Concern - include ActionDispatch::Routing::UrlFor include TagHelper - # We need to override url_options, _routes_context - # and optimize_routes_generation? to consider the controller. - - def url_options #:nodoc: - return super unless controller.respond_to?(:url_options) - controller.url_options - end - - def _routes_context #:nodoc: - controller - end - protected :_routes_context - - def optimize_routes_generation? #:nodoc: - controller.respond_to?(:optimize_routes_generation?) ? - controller.optimize_routes_generation? : super + module ClassMethods + def _url_for_modules + ActionView::RoutingUrlFor + end end - protected :optimize_routes_generation? - # 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 - # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action" - # instead of the fully qualified URL like "http://example.com/controller/action". - # - # ==== Options - # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. - # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified). - # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this - # is currently not recommended since it breaks caching. - # * <tt>:host</tt> - Overrides the default (current) host if provided. - # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided. - # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present). - # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present). - # - # ==== Relying on named routes - # - # Passing a record (like an Active Record) instead of a hash as the options parameter will - # trigger the named route for that record. The lookup will happen on the name of the class. So passing a - # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as - # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). - # - # ==== Implicit Controller Namespacing - # - # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. - # - # ==== Examples - # <%= url_for(:action => 'index') %> - # # => /blog/ - # - # <%= url_for(:action => 'find', :controller => 'books') %> - # # => /books/find - # - # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %> - # # => https://www.example.com/members/login/ - # - # <%= url_for(:action => 'play', :anchor => 'player') %> - # # => /messages/play/#player - # - # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %> - # # => /testing/jump/#tax&ship - # - # <%= url_for(Workshop.new) %> - # # relies on Workshop answering a persisted? call (and in this case returning false) - # # => /workshops - # - # <%= url_for(@workshop) %> - # # calls @workshop.to_param which by default returns the id - # # => /workshops/5 - # - # # to_param can be re-defined in a model to provide different URL names: - # # => /workshops/1-workshop-name - # - # <%= url_for("http://www.example.com") %> - # # => http://www.example.com - # - # <%= url_for(:back) %> - # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" - # # => http://www.example.com - # - # <%= url_for(:back) %> - # # if request.env["HTTP_REFERER"] is not set or is blank - # # => javascript:history.back() - # - # <%= url_for(:action => 'index', :controller => 'users') %> - # # Assuming an "admin" namespace - # # => /admin/users - # - # <%= url_for(:action => 'index', :controller => '/users') %> - # # Specify absolute path with beginning slash - # # => /users - def url_for(options = nil) + # Basic implementation of url_for to allow use helpers without routes existence + def url_for(options = nil) # :nodoc: case options when String options - when nil, Hash - options ||= {} - options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys) - super when :back - controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' + _back_url else - polymorphic_path(options) + raise ArgumentError, "arguments passed to url_for can't be handled. Please require " + + "routes or provide your own implementation" end end + def _back_url # :nodoc: + referrer = controller.respond_to?(:request) && controller.request.env["HTTP_REFERER"] + referrer || 'javascript:history.back()' + end + protected :_back_url + + # Creates a link tag of the given +name+ using a URL created by the set of +options+. # See the valid options in the documentation for +url_for+. It's also possible to # pass a String instead of an options hash, which generates a link tag that uses the diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index f0ea92b018..af367ac28d 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -43,7 +43,7 @@ module ActionView end register_detail(:locale) { [I18n.locale, I18n.default_locale].uniq } - register_detail(:formats) { Mime::SET.symbols } + register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } register_detail(:handlers){ Template::Handlers.extensions } class DetailsKey #:nodoc: diff --git a/actionpack/lib/action_view/model_naming.rb b/actionpack/lib/action_view/model_naming.rb new file mode 100644 index 0000000000..e09ebd60df --- /dev/null +++ b/actionpack/lib/action_view/model_naming.rb @@ -0,0 +1,12 @@ +module ActionView + module ModelNaming + # Converts the given object to an ActiveModel compliant one. + def convert_to_model(object) + object.respond_to?(:to_model) ? object.to_model : object + end + + def model_name_from_record_or_class(record_or_class) + (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name + end + end +end diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb new file mode 100644 index 0000000000..2953654972 --- /dev/null +++ b/actionpack/lib/action_view/record_identifier.rb @@ -0,0 +1,84 @@ +require 'active_support/core_ext/module' +require 'action_view/model_naming' + +module ActionView + # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or + # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to + # a higher logical level. + # + # # routes + # resources :posts + # + # # view + # <%= div_for(post) do %> <div id="post_45" class="post"> + # <%= post.body %> What a wonderful world! + # <% end %> </div> + # + # # controller + # def update + # post = Post.find(params[:id]) + # post.update_attributes(params[:post]) + # + # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) + # end + # + # As the example above shows, you can stop caring to a large extent what the actual id of the post is. + # You just know that one is being assigned and that the subsequent calls in redirect_to expect that + # same naming convention and allows you to write less code if you follow it. + module RecordIdentifier + extend self + extend ModelNaming + + include ModelNaming + + JOIN = '_'.freeze + NEW = 'new'.freeze + + # The DOM class convention is to use the singular form of an object or class. + # + # dom_class(post) # => "post" + # dom_class(Person) # => "person" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_class: + # + # dom_class(post, :edit) # => "edit_post" + # dom_class(Person, :edit) # => "edit_person" + def dom_class(record_or_class, prefix = nil) + singular = model_name_from_record_or_class(record_or_class).param_key + prefix ? "#{prefix}#{JOIN}#{singular}" : singular + end + + # The DOM id convention is to use the singular form of an object or class with the id following an underscore. + # If no id is found, prefix with "new_" instead. + # + # dom_id(Post.find(45)) # => "post_45" + # dom_id(Post.new) # => "new_post" + # + # If you need to address multiple instances of the same class in the same view, you can prefix the dom_id: + # + # dom_id(Post.find(45), :edit) # => "edit_post_45" + # dom_id(Post.new, :custom) # => "custom_post" + def dom_id(record, prefix = nil) + if record_id = record_key_for_dom_id(record) + "#{dom_class(record, prefix)}#{JOIN}#{record_id}" + else + dom_class(record, prefix || NEW) + end + end + + protected + + # Returns a string representation of the key attribute(s) that is suitable for use in an HTML DOM id. + # This can be overwritten to customize the default generated string representation if desired. + # If you need to read back a key from a dom_id in order to query for the underlying database record, + # you should write a helper like 'person_record_from_dom_id' that will extract the key either based + # on the default implementation (which just joins all key attributes with '_') or on your own + # overwritten version of the method. By default, this implementation passes the key string through a + # method that replaces all characters that are invalid inside DOM ids, with valid ones. You need to + # make sure yourself that your dom ids are valid, in case you overwrite this method. + def record_key_for_dom_id(record) + key = convert_to_model(record).to_key + key ? key.join('_') : key + end + end +end diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb index f46aabd2be..9cf6eb0c65 100644 --- a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -30,7 +30,7 @@ module ActionView # 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 = ActionController::Base.logger + logger = ActionView::Base.logger return unless logger message = "\n#{exception.class} (#{exception.message}):\n" diff --git a/actionpack/lib/action_view/routing_url_for.rb b/actionpack/lib/action_view/routing_url_for.rb new file mode 100644 index 0000000000..13fb6406e1 --- /dev/null +++ b/actionpack/lib/action_view/routing_url_for.rb @@ -0,0 +1,107 @@ +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 + # <tt>:only_path</tt> is <tt>true</tt> so you'll get the relative "/controller/action" + # instead of the fully qualified URL like "http://example.com/controller/action". + # + # ==== Options + # * <tt>:anchor</tt> - Specifies the anchor name to be appended to the path. + # * <tt>:only_path</tt> - If true, returns the relative URL (omitting the protocol, host name, and port) (<tt>true</tt> by default unless <tt>:host</tt> is specified). + # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2005/". Note that this + # is currently not recommended since it breaks caching. + # * <tt>:host</tt> - Overrides the default (current) host if provided. + # * <tt>:protocol</tt> - Overrides the default (current) protocol if provided. + # * <tt>:user</tt> - Inline HTTP authentication (only plucked out if <tt>:password</tt> is also present). + # * <tt>:password</tt> - Inline HTTP authentication (only plucked out if <tt>:user</tt> is also present). + # + # ==== Relying on named routes + # + # Passing a record (like an Active Record) instead of a hash as the options parameter will + # trigger the named route for that record. The lookup will happen on the name of the class. So passing a + # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as + # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). + # + # ==== Implicit Controller Namespacing + # + # Controllers passed in using the +:controller+ option will retain their namespace unless it is an absolute one. + # + # ==== Examples + # <%= url_for(:action => 'index') %> + # # => /blog/ + # + # <%= url_for(:action => 'find', :controller => 'books') %> + # # => /books/find + # + # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %> + # # => https://www.example.com/members/login/ + # + # <%= url_for(:action => 'play', :anchor => 'player') %> + # # => /messages/play/#player + # + # <%= url_for(:action => 'jump', :anchor => 'tax&ship') %> + # # => /testing/jump/#tax&ship + # + # <%= url_for(Workshop.new) %> + # # relies on Workshop answering a persisted? call (and in this case returning false) + # # => /workshops + # + # <%= url_for(@workshop) %> + # # calls @workshop.to_param which by default returns the id + # # => /workshops/5 + # + # # to_param can be re-defined in a model to provide different URL names: + # # => /workshops/1-workshop-name + # + # <%= url_for("http://www.example.com") %> + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is not set or is blank + # # => javascript:history.back() + # + # <%= url_for(:action => 'index', :controller => 'users') %> + # # Assuming an "admin" namespace + # # => /admin/users + # + # <%= url_for(:action => 'index', :controller => '/users') %> + # # Specify absolute path with beginning slash + # # => /users + def url_for(options = nil) + case options + when String + options + when nil, Hash + options ||= {} + options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys) + super + when :back + _back_url + else + polymorphic_path(options) + end + end + + def url_options #:nodoc: + return super unless controller.respond_to?(:url_options) + controller.url_options + end + + def _routes_context #:nodoc: + controller + end + protected :_routes_context + + def optimize_routes_generation? #:nodoc: + controller.respond_to?(:optimize_routes_generation?) ? + controller.optimize_routes_generation? : super + end + protected :optimize_routes_generation? + end +end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index a04eac1d3f..379cdc8a25 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/deprecation' require 'thread' module ActionView @@ -92,6 +93,7 @@ module ActionView autoload :Error autoload :Handlers autoload :Text + autoload :Types end extend Template::Handlers @@ -121,7 +123,7 @@ module ActionView @locals = details[:locals] || [] @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now - @formats = Array(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f } + @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } @compile_mutex = Mutex.new end @@ -147,9 +149,14 @@ module ActionView end def mime_type + ActiveSupport::Deprecation.warn 'Template#mime_type is deprecated and will be removed in Rails 4.1. Please use type method instead.' @mime_type ||= Mime::Type.lookup_by_extension(@formats.first.to_s) if @formats.first end + def type + @type ||= Types[@formats.first] if @formats.first + 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 diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index f2bef4bded..e00056781d 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -8,6 +8,9 @@ module ActionView class EncodingError < StandardError #:nodoc: end + class MissingRequestError < StandardError #:nodoc: + end + class WrongEncodingError < EncodingError #:nodoc: def initialize(string, encoding) @string, @encoding = string, encoding diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb index 34397c3bcf..d90b0c6378 100644 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ b/actionpack/lib/action_view/template/handlers/builder.rb @@ -3,7 +3,7 @@ module ActionView class Builder # Default format used by Builder. class_attribute :default_format - self.default_format = Mime::XML + self.default_format = :xml def call(template) require_engine diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 2bb656fac9..25c6fd4aa8 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -235,7 +235,7 @@ module ActionView extension = pieces.pop ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension handler = Template.handler_for_extension(extension) - format = pieces.last && Mime[pieces.last] + format = pieces.last && Template::Types[pieces.last] [handler, format] end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 3af76dfcdb..859c7bc3ce 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -2,12 +2,12 @@ module ActionView #:nodoc: # = Action View Text Template class Template class Text #:nodoc: - attr_accessor :mime_type + attr_accessor :type - def initialize(string, mime_type = nil) - @string = string.to_s - @mime_type = Mime[mime_type] || mime_type if mime_type - @mime_type ||= Mime::TEXT + def initialize(string, type = nil) + @string = string.to_s + @type = Types[type] || type if type + @type ||= Types[:text] end def identifier @@ -27,7 +27,7 @@ module ActionView #:nodoc: end def formats - [@mime_type.to_sym] + [@type.to_sym] end end end diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb new file mode 100644 index 0000000000..7611c9e708 --- /dev/null +++ b/actionpack/lib/action_view/template/types.rb @@ -0,0 +1,58 @@ +require 'set' +require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/object/blank' + +module ActionView + class Template + class Types + class Type + cattr_accessor :types + self.types = Set.new + + def self.register(*t) + types.merge(t.map { |type| type.to_s }) + end + + register :html, :text, :js, :css, :xml, :json + + def self.[](type) + return type if type.is_a?(self) + + if type.is_a?(Symbol) || types.member?(type.to_s) + new(type) + end + end + + attr_reader :symbol + + def initialize(symbol) + @symbol = symbol.to_sym + end + + delegate :to_s, :to_sym, :to => :symbol + alias to_str to_s + + def ref + to_sym || to_s + end + + def ==(type) + return false if type.blank? + symbol.to_sym == type.to_sym + end + end + + cattr_accessor :type_klass + + def self.delegate_to(klass) + self.type_klass = klass + end + + delegate_to Type + + def self.[](type) + type_klass[type] + end + end + end +end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index 55f79bf761..434fc8cc14 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -38,10 +38,11 @@ module ActionView include ActionView::Context include ActionDispatch::Routing::PolymorphicRoutes - include ActionController::RecordIdentifier include AbstractController::Helpers include ActionView::Helpers + include ActionView::RecordIdentifier + include ActionView::RoutingUrlFor delegate :lookup_context, :to => :controller attr_accessor :controller, :output_buffer, :rendered diff --git a/actionpack/lib/action_view/vendor/html-scanner.rb b/actionpack/lib/action_view/vendor/html-scanner.rb new file mode 100644 index 0000000000..879b31e60e --- /dev/null +++ b/actionpack/lib/action_view/vendor/html-scanner.rb @@ -0,0 +1,20 @@ +$LOAD_PATH << "#{File.dirname(__FILE__)}/html-scanner" + +module HTML + extend ActiveSupport::Autoload + + eager_autoload do + autoload :CDATA, 'html/node' + autoload :Document, 'html/document' + autoload :FullSanitizer, 'html/sanitizer' + autoload :LinkSanitizer, 'html/sanitizer' + autoload :Node, 'html/node' + autoload :Sanitizer, 'html/sanitizer' + autoload :Selector, 'html/selector' + autoload :Tag, 'html/node' + autoload :Text, 'html/node' + autoload :Tokenizer, 'html/tokenizer' + autoload :Version, 'html/version' + autoload :WhiteListSanitizer, 'html/sanitizer' + end +end diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_view/vendor/html-scanner/html/document.rb index 386820300a..386820300a 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/document.rb diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb b/actionpack/lib/action_view/vendor/html-scanner/html/node.rb index 4e1f016431..4e1f016431 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/node.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/node.rb diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb index 6b4ececda2..6b4ececda2 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/sanitizer.rb diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb index 1eadfc0390..1eadfc0390 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/selector.rb diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb b/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb index 8ac8d34430..8ac8d34430 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/tokenizer.rb diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb b/actionpack/lib/action_view/vendor/html-scanner/html/version.rb index 6d645c3e14..6d645c3e14 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/version.rb +++ b/actionpack/lib/action_view/vendor/html-scanner/html/version.rb |