diff options
Diffstat (limited to 'actionview/lib')
46 files changed, 525 insertions, 274 deletions
diff --git a/actionview/lib/action_view.rb b/actionview/lib/action_view.rb index 39c0c6c856..50712e0830 100644 --- a/actionview/lib/action_view.rb +++ b/actionview/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2013 David Heinemeier Hansson +# Copyright (c) 2004-2014 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -23,10 +23,13 @@ require 'active_support' require 'active_support/rails' +require 'action_view/version' module ActionView extend ActiveSupport::Autoload + ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' + eager_autoload do autoload :Base autoload :Context @@ -53,7 +56,6 @@ module ActionView autoload_at "action_view/template/resolver" do autoload :Resolver autoload :PathResolver - autoload :FileSystemResolver autoload :OptimizedFileSystemResolver autoload :FallbackFileSystemResolver end @@ -80,10 +82,9 @@ module ActionView autoload :TestCase - ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*' - def self.eager_load! super + ActionView::Helpers.eager_load! ActionView::Template.eager_load! HTML.eager_load! end diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb index caade8f43b..455ce531ae 100644 --- a/actionview/lib/action_view/base.rb +++ b/actionview/lib/action_view/base.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/module/attr_internal' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/ordered_options' require 'action_view/log_subscriber' require 'action_view/helpers' @@ -153,6 +153,10 @@ module ActionView #:nodoc: # Specify default_formats that can be rendered. cattr_accessor :default_formats + # Specify whether an error should be raised for missing translations + cattr_accessor :raise_on_missing_translations + @@raise_on_missing_translations = false + class_attribute :_routes class_attribute :logger diff --git a/actionview/lib/action_view/dependency_tracker.rb b/actionview/lib/action_view/dependency_tracker.rb index b2e8334077..0ccf2515c5 100644 --- a/actionview/lib/action_view/dependency_tracker.rb +++ b/actionview/lib/action_view/dependency_tracker.rb @@ -1,7 +1,7 @@ require 'thread_safe' module ActionView - class DependencyTracker + class DependencyTracker # :nodoc: @trackers = ThreadSafe::Cache.new def self.find_dependencies(name, template) @@ -23,24 +23,52 @@ module ActionView @trackers.delete(handler) end - class ERBTracker + class ERBTracker # :nodoc: EXPLICIT_DEPENDENCY = /# Template Dependency: (\S+)/ + # A valid ruby identifier - suitable for class, method and specially variable names + IDENTIFIER = / + [[:alpha:]_] # at least one uppercase letter, lowercase letter or underscore + [[:word:]]* # followed by optional letters, numbers or underscores + /x + + # Any kind of variable name. e.g. @instance, @@class, $global or local. + # Possibly following a method call chain + VARIABLE_OR_METHOD_CHAIN = / + (?:\$|@{1,2})? # optional global, instance or class variable indicator + (?:#{IDENTIFIER}\.)* # followed by an optional chain of zero-argument method calls + (?<dynamic>#{IDENTIFIER}) # and a final valid identifier, captured as DYNAMIC + /x + + # A simple string literal. e.g. "School's out!" + STRING = / + (?<quote>['"]) # an opening quote + (?<static>.*?) # with anything inside, captured as STATIC + \k<quote> # and a matching closing quote + /x + + # Part of any hash containing the :partial key + PARTIAL_HASH_KEY = / + (?:\bpartial:|:partial\s*=>) # partial key in either old or new style hash syntax + \s* # followed by optional spaces + /x + # Matches: - # render partial: "comments/comment", collection: commentable.comments - # render "comments/comments" - # render 'comments/comments' - # render('comments/comments') + # partial: "comments/comment", collection: @all_comments => "comments/comment" + # (object: @single_comment, partial: "comments/comment") => "comments/comment" # - # 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"'][@\w\/\."']+) # the template name itself -- 2nd capture - /x + # "comments/comments" + # 'comments/comments' + # ('comments/comments') + # + # (@topic) => "topics/topic" + # topics => "topics/topic" + # (message.topics) => "topics/topic" + RENDER_ARGUMENTS = /\A + (?:\s*\(?\s*) # optional opening paren surrounded by spaces + (?:.*?#{PARTIAL_HASH_KEY})? # optional hash, up to the partial key declaration + (?:#{STRING}|#{VARIABLE_OR_METHOD_CHAIN}) # finally, the dependency name of interest + /xm def self.call(name, template) new(name, template).dependencies @@ -68,19 +96,33 @@ module ActionView end def render_dependencies - source.scan(RENDER_DEPENDENCY). - collect(&:second).uniq. + render_dependencies = [] + render_calls = source.split(/\brender\b/).drop(1) - # 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_calls.each do |arguments| + arguments.scan(RENDER_ARGUMENTS) do + add_dynamic_dependency(render_dependencies, Regexp.last_match[:dynamic]) + add_static_dependency(render_dependencies, Regexp.last_match[:static]) + end + end - # render("headline") => render("message/headline") - collect { |name| name.include?("/") ? name : "#{directory}/#{name}" }. + render_dependencies.uniq + end + + def add_dynamic_dependency(dependencies, dependency) + if dependency + dependencies << "#{dependency.pluralize}/#{dependency.singularize}" + end + end - # replace quotes from string renders - collect { |name| name.gsub(/["']/, "") } + def add_static_dependency(dependencies, dependency) + if dependency + if dependency.include?('/') + dependencies << dependency + else + dependencies << "#{directory}/#{dependency}" + end + end end def explicit_dependencies diff --git a/actionview/lib/action_view/digestor.rb b/actionview/lib/action_view/digestor.rb index 5570e2a8dc..40d493da64 100644 --- a/actionview/lib/action_view/digestor.rb +++ b/actionview/lib/action_view/digestor.rb @@ -9,23 +9,53 @@ module ActionView @@digest_monitor = Monitor.new class << self - def digest(name, format, finder, options = {}) - details_key = finder.details_key.hash + # Supported options: + # + # * <tt>name</tt> - Template name + # * <tt>format</tt> - Template format + # * <tt>variant</tt> - Variant of +format+ (optional) + # * <tt>finder</tt> - An instance of ActionView::LookupContext + # * <tt>dependencies</tt> - An array of dependent views + # * <tt>partial</tt> - Specifies whether the template is a partial + def digest(options_or_deprecated_name, *deprecated_args) + options = _options_for_digest(options_or_deprecated_name, *deprecated_args) + + details_key = options[:finder].details_key.hash dependencies = Array.wrap(options[:dependencies]) - cache_key = ([name, details_key, format] + dependencies).join('.') + cache_key = ([options[:name], details_key, options[:format], options[:variant]].compact + dependencies).join('.') # this is a correctly done double-checked locking idiom # (ThreadSafe::Cache's lookups have volatile semantics) @@cache[cache_key] || @@digest_monitor.synchronize do @@cache.fetch(cache_key) do # re-check under lock - compute_and_store_digest(cache_key, name, format, finder, options) + compute_and_store_digest(cache_key, options) end end end + def _options_for_digest(options_or_deprecated_name, *deprecated_args) + if !options_or_deprecated_name.is_a?(Hash) + ActiveSupport::Deprecation.warn \ + 'ActionView::Digestor.digest changed its method signature from ' \ + '#digest(name, format, finder, options) to #digest(options), ' \ + 'with options now including :name, :format, :finder, and ' \ + ':variant. Please update your code to pass a Hash argument. ' \ + 'Support for the old method signature will be removed in Rails 5.0.' + + _options_for_digest (deprecated_args[2] || {}).merge \ + name: options_or_deprecated_name, + format: deprecated_args[0], + finder: deprecated_args[1] + else + options_or_deprecated_name.assert_valid_keys(:name, :format, :variant, :finder, :dependencies, :partial) + options_or_deprecated_name + end + end + private - def compute_and_store_digest(cache_key, name, format, finder, options) # called under @@digest_monitor lock - klass = if options[:partial] || name.include?("/_") + + def compute_and_store_digest(cache_key, options) # called under @@digest_monitor lock + klass = if options[:partial] || options[:name].include?("/_") # Prevent re-entry or else recursive templates will blow the stack. # There is no need to worry about other threads seeing the +false+ value, # as they will then have to wait for this thread to let go of the @@digest_monitor lock. @@ -35,20 +65,22 @@ module ActionView Digestor end - digest = klass.new(name, format, finder, options).digest + digest = klass.new(options).digest # Store the actual digest if config.cache_template_loading is true @@cache[cache_key] = stored_digest = digest if ActionView::Resolver.caching? digest ensure # something went wrong or ActionView::Resolver.caching? is false, make sure not to corrupt the @@cache - @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest + @@cache.delete_pair(cache_key, false) if pre_stored && !stored_digest end end - attr_reader :name, :format, :finder, :options + attr_reader :name, :format, :variant, :finder, :options - def initialize(name, format, finder, options={}) - @name, @format, @finder, @options = name, format, finder, options + def initialize(options_or_deprecated_name, *deprecated_args) + options = self.class._options_for_digest(options_or_deprecated_name, *deprecated_args) + @options = options.except(:name, :format, :variant, :finder) + @name, @format, @variant, @finder = options.values_at(:name, :format, :variant, :finder) end def digest @@ -68,7 +100,7 @@ module ActionView def nested_dependencies dependencies.collect do |dependency| - dependencies = PartialDigestor.new(dependency, format, finder).nested_dependencies + dependencies = PartialDigestor.new(name: dependency, format: format, finder: finder).nested_dependencies dependencies.any? ? { dependency => dependencies } : dependency end end @@ -88,7 +120,11 @@ module ActionView end def template - @template ||= finder.find(logical_name, [], partial?, formats: [ format ]) + @template ||= begin + finder.disable_cache do + finder.find(logical_name, [], partial?, [], formats: [format], variants: [variant]) + end + end end def source @@ -97,7 +133,7 @@ module ActionView def dependency_digest template_digests = dependencies.collect do |template_name| - Digestor.digest(template_name, format, finder, partial: true) + Digestor.digest(name: template_name, format: format, finder: finder, partial: true) end (template_digests + injected_dependencies).join("-") diff --git a/actionview/lib/action_view/gem_version.rb b/actionview/lib/action_view/gem_version.rb new file mode 100644 index 0000000000..9266e55c47 --- /dev/null +++ b/actionview/lib/action_view/gem_version.rb @@ -0,0 +1,15 @@ +module ActionView + # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt> + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 4 + MINOR = 2 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/actionview/lib/action_view/helpers.rb b/actionview/lib/action_view/helpers.rb index 8a78685ae1..787e9d67b2 100644 --- a/actionview/lib/action_view/helpers.rb +++ b/actionview/lib/action_view/helpers.rb @@ -27,6 +27,12 @@ module ActionView #:nodoc: autoload :TextHelper autoload :TranslationHelper autoload :UrlHelper + autoload :Tags + + def self.eager_load! + super + Tags.eager_load! + end extend ActiveSupport::Concern diff --git a/actionview/lib/action_view/helpers/active_model_helper.rb b/actionview/lib/action_view/helpers/active_model_helper.rb index 901f433c70..d5222e3616 100644 --- a/actionview/lib/action_view/helpers/active_model_helper.rb +++ b/actionview/lib/action_view/helpers/active_model_helper.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/enumerable' module ActionView diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index e2430140c2..aa49f1edc1 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -103,7 +103,7 @@ module ActionView }.join("\n").html_safe end - # Returns a link tag that browsers and news readers can use to auto-detect + # Returns a link tag that browsers and feed readers can use to auto-detect # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or # <tt>:atom</tt>. Control the link options in url_for format using the # +url_options+. You can modify the LINK tag itself in +tag_options+. @@ -153,14 +153,14 @@ module ActionView # # ==== Examples # - # favicon_link_tag '/myicon.ico' + # favicon_link_tag 'myicon.ico' # # => <link href="/assets/myicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon" /> # # Mobile Safari looks for a different <link> tag, pointing to an image that # will be used if you add the page to the home screen of an iPod Touch, iPhone, or iPad. # The following call would generate such a tag: # - # favicon_link_tag '/mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' + # favicon_link_tag 'mb-icon.png', rel: 'apple-touch-icon', type: 'image/png' # # => <link href="/assets/mb-icon.png" rel="apple-touch-icon" type="image/png" /> def favicon_link_tag(source='favicon.ico', options={}) tag('link', { @@ -176,7 +176,7 @@ module ActionView # ==== Options # # You can add HTML attributes using the +options+. The +options+ supports - # three additional keys for convenience and conformance: + # two additional keys for convenience and conformance: # # * <tt>:alt</tt> - If no alt text is given, the file name part of the # +source+ is used (capitalized and without the extension) @@ -207,11 +207,7 @@ module ActionView options[:alt] = options.fetch(:alt){ image_alt(src) } end - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{\A\d+x\d+\z} - options[:width] = options[:height] = size if size =~ %r{\A\d+\z} - end - + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] tag("img", options) end @@ -248,9 +244,9 @@ module ActionView # # * <tt>:poster</tt> - Set an image (like a screenshot) to be shown # before the video loads. The path is calculated like the +src+ of +image_tag+. - # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes - # width="30" and height="45". <tt>:size</tt> will be ignored if the - # value is not in the correct format. + # * <tt>:size</tt> - Supplied as "{Width}x{Height}" or "{Number}", so "30x45" becomes + # width="30" and height="45", and "50" becomes width="50" and height="50". + # <tt>:size</tt> will be ignored if the value is not in the correct format. # # ==== Examples # @@ -264,6 +260,8 @@ module ActionView # # => <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> # video_tag("/trailers/hd.avi", size: "16x16") # # => <video src="/trailers/hd.avi" width="16" height="16" /> + # video_tag("/trailers/hd.avi", size: "16") + # # => <video height="16" src="/trailers/hd.avi" width="16" /> # video_tag("/trailers/hd.avi", height: '32', width: '32') # # => <video height="32" src="/trailers/hd.avi" width="32" /> # video_tag("trailer.ogg", "trailer.flv") @@ -275,10 +273,7 @@ module ActionView def video_tag(*sources) multiple_sources_tag('video', sources) do |options| options[:poster] = path_to_image(options[:poster]) if options[:poster] - - if size = options.delete(:size) - options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} - end + options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size] end end @@ -314,6 +309,14 @@ module ActionView content_tag(type, nil, options) end end + + def extract_dimensions(size) + if size =~ %r{\A\d+x\d+\z} + size.split('x') + elsif size =~ %r{\A\d+\z} + [size, size] + end + end end end end diff --git a/actionview/lib/action_view/helpers/atom_feed_helper.rb b/actionview/lib/action_view/helpers/atom_feed_helper.rb index 42b1dd8933..af70a4242a 100644 --- a/actionview/lib/action_view/helpers/atom_feed_helper.rb +++ b/actionview/lib/action_view/helpers/atom_feed_helper.rb @@ -64,7 +64,7 @@ module ActionView # 'xmlns:openSearch' => 'http://a9.com/-/spec/opensearch/1.1/'}) do |feed| # feed.title("My great blog!") # feed.updated((@posts.first.created_at)) - # feed.tag!(openSearch:totalResults, 10) + # feed.tag!('openSearch:totalResults', 10) # # @posts.each do |post| # feed.entry(post) do |entry| diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index b3af1d4da4..3177d18c4d 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -165,10 +165,20 @@ module ActionView def fragment_name_with_digest(name) #:nodoc: 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, dependencies: view_cache_dependencies) - ] + variant = request.variant.is_a?(Array) ? request.variant.first : request.variant + + options = { + name: @virtual_path, + format: formats.last.to_sym, + variant: variant, + finder: lookup_context, + dependencies: view_cache_dependencies + } + + names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) + digest = Digestor.digest(options) + + [*names, digest] else name end diff --git a/actionview/lib/action_view/helpers/csrf_helper.rb b/actionview/lib/action_view/helpers/csrf_helper.rb index eeb0ed94b9..5af92c4ff2 100644 --- a/actionview/lib/action_view/helpers/csrf_helper.rb +++ b/actionview/lib/action_view/helpers/csrf_helper.rb @@ -12,8 +12,11 @@ module ActionView # These are used to generate the dynamic forms that implement non-remote links with # <tt>:method</tt>. # - # Note that regular forms generate hidden fields, and that Ajax calls are whitelisted, - # so they do not use these tags. + # You don't need to use these tags for regular forms as they generate their own hidden fields. + # + # For AJAX requests other than GETs, extract the "csrf-token" from the meta-tag and send as the + # "X-CSRF-Token" HTTP header. If you are using jQuery with jquery-rails this happens automatically. + # def csrf_meta_tags if protect_against_forgery? [ diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index d7e8e99200..1738df9cac 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -19,6 +19,10 @@ module ActionView # the <tt>select_month</tt> method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead # of \date[month]. module DateHelper + MINUTES_IN_YEAR = 525600 + MINUTES_IN_QUARTER_YEAR = 131400 + MINUTES_IN_THREE_QUARTERS_YEAR = 394200 + # Reports the approximate distance in time between two Time, Date or DateTime objects or integers as seconds. # Pass <tt>include_seconds: true</tt> if you want more detailed approximations when distance < 1 min, 29 secs. # Distances are reported based on the following table: @@ -50,19 +54,19 @@ module ActionView # distance_of_time_in_words(from_time, from_time + 50.minutes) # => about 1 hour # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute - # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds + # distance_of_time_in_words(from_time, from_time + 15.seconds, include_seconds: true) # => less than 20 seconds # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days - # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute - # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute + # distance_of_time_in_words(from_time, from_time + 45.seconds, include_seconds: true) # => less than a minute + # distance_of_time_in_words(from_time, from_time - 45.seconds, include_seconds: true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years # # to_time = Time.now + 6.years + 19.days - # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years - # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years + # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years + # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute def distance_of_time_in_words(from_time, to_time = 0, options = {}) options = { @@ -115,16 +119,16 @@ module ActionView # e.g. if there are 20 leap year days between 2 dates having the same day # and month then the based on 365 days calculation # the distance in years will come out to over 80 years when in written - # english it would read better as about 80 years. + # English it would read better as about 80 years. minutes_with_offset = distance_in_minutes - minute_offset_for_leap_year else minutes_with_offset = distance_in_minutes end - remainder = (minutes_with_offset % 525600) - distance_in_years = (minutes_with_offset.div 525600) - if remainder < 131400 + remainder = (minutes_with_offset % MINUTES_IN_YEAR) + distance_in_years = (minutes_with_offset.div MINUTES_IN_YEAR) + if remainder < MINUTES_IN_QUARTER_YEAR locale.t(:about_x_years, :count => distance_in_years) - elsif remainder < 394200 + elsif remainder < MINUTES_IN_THREE_QUARTERS_YEAR locale.t(:over_x_years, :count => distance_in_years) else locale.t(:almost_x_years, :count => distance_in_years + 1) @@ -169,9 +173,16 @@ module ActionView # "2 - February" instead of "February"). # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names. # Note: You can also use Rails' i18n functionality for this. + # * <tt>:month_format_string</tt> - Set to a format string. The string gets passed keys +:number+ (integer) + # and +:name+ (string). A format string would be something like "%{name} (%<number>02d)" for example. + # See <tt>Kernel.sprintf</tt> for documentation on format sequences. # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing). - # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>. - # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>. + # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Date.today.year - 5</tt>if + # you are creating new record. While editing existing record, <tt>:start_year</tt> defaults to + # the current selected year minus 5. + # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if + # you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to + # the current selected year plus 5. # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the # first of the given month in order to not create invalid dates like 31 February. @@ -846,24 +857,36 @@ module ActionView I18n.translate(key, :locale => @options[:locale]) end - # Lookup month name for number. - # month_name(1) => "January" + # Looks up month names by number (1-based): + # + # month_name(1) # => "January" + # + # If the <tt>:use_month_numbers</tt> option is passed: + # + # month_name(1) # => 1 + # + # If the <tt>:use_two_month_numbers</tt> option is passed: + # + # month_name(1) # => '01' + # + # If the <tt>:add_month_numbers</tt> option is passed: + # + # month_name(1) # => "1 - January" # - # If <tt>:use_month_numbers</tt> option is passed - # month_name(1) => 1 + # If the <tt>:month_format_string</tt> option is passed: # - # If <tt>:use_two_month_numbers</tt> option is passed - # month_name(1) => '01' + # month_name(1) # => "January (01)" # - # If <tt>:add_month_numbers</tt> option is passed - # month_name(1) => "1 - January" + # depending on the format string. def month_name(number) if @options[:use_month_numbers] number elsif @options[:use_two_digit_numbers] - sprintf "%02d", number + '%02d' % number elsif @options[:add_month_numbers] "#{number} - #{month_names[number]}" + elsif format_string = @options[:month_format_string] + format_string % {number: number, name: month_names[number]} else month_names[number] end @@ -1062,7 +1085,7 @@ module ActionView # Wraps ActionView::Helpers::DateHelper#datetime_select for form builders: # # <%= form_for @person do |f| %> - # <%= f.time_select :last_request_at %> + # <%= f.datetime_select :last_request_at %> # <%= f.submit %> # <% end %> # diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb index 38d969ed0c..1ff090f244 100644 --- a/actionview/lib/action_view/helpers/form_helper.rb +++ b/actionview/lib/action_view/helpers/form_helper.rb @@ -3,9 +3,8 @@ require 'action_view/helpers/date_helper' 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/module/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/inflections' @@ -52,7 +51,7 @@ module ActionView # The HTML generated for this would be (modulus formatting): # # <form action="/people" class="new_person" id="new_person" method="post"> - # <div style="margin:0;padding:0;display:inline"> + # <div style="display:none"> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: @@ -82,7 +81,7 @@ module ActionView # the code above as is would yield instead: # # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> - # <div style="margin:0;padding:0;display:inline"> + # <div style="display:none"> # <input name="_method" type="hidden" value="patch" /> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> @@ -316,7 +315,7 @@ module ActionView # The HTML generated for this would be: # # <form action='http://www.example.com' method='post' data-remote='true'> - # <div style='margin:0;padding:0;display:inline'> + # <div style='display:none'> # <input name='_method' type='hidden' value='patch' /> # </div> # ... @@ -334,7 +333,7 @@ module ActionView # The HTML generated for this would be: # # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> - # <div style='margin:0;padding:0;display:inline'> + # <div style='display:none'> # <input name='_method' type='hidden' value='patch' /> # </div> # ... @@ -458,7 +457,7 @@ module ActionView # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to @@ -763,8 +762,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -1269,7 +1268,7 @@ module ActionView # doesn't create the form tags themselves. This makes fields_for suitable # for specifying additional model objects in the same form. # - # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # Although the usage and purpose of +fields_for+ is similar to +form_for+'s, # its method signature is slightly different. Like +form_for+, it yields # a FormBuilder object associated with a particular model object to a block, # and within the block allows methods to be called on the builder to diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index 4347983bad..f625a9ff49 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -260,7 +260,7 @@ module ActionView Tags::GroupedCollectionSelect.new(object, method, self, collection, group_method, group_label_method, option_key_method, option_value_method, options, html_options).render end - # Return select and option tags for the given object and method, using + # Returns select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # # In addition to the <tt>:include_blank</tt> option documented above, diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 142c27ace0..0bbe08166b 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -465,17 +465,23 @@ module ActionView # # <strong>Ask me!</strong> # # </button> # - # button_tag "Checkout", data: { disable_with => "Please wait..." } + # button_tag "Checkout", data: { disable_with: "Please wait..." } # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> # def button_tag(content_or_options = nil, options = nil, &block) - options = content_or_options if block_given? && content_or_options.is_a?(Hash) - options ||= {} - options = options.stringify_keys + if content_or_options.is_a? Hash + options = content_or_options + else + options ||= {} + end - options.reverse_merge! 'name' => 'button', 'type' => 'submit' + options = { 'name' => 'button', 'type' => 'submit' }.merge!(options.stringify_keys) - content_tag :button, content_or_options || 'Button', options, &block + if block_given? + content_tag :button, options, &block + else + content_tag :button, content_or_options || 'Button', options + end end # Displays an image which when clicked will submit the form. @@ -722,7 +728,7 @@ module ActionView enforce_utf8 = html_options.delete("enforce_utf8") { true } tags = (enforce_utf8 ? utf8_enforcer_tag : ''.html_safe) << method_tag - content_tag(:div, tags, :style => 'margin:0;padding:0;display:inline') + content_tag(:div, tags, :style => 'display:none') end def form_tag_html(html_options) diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index fda7038a5d..7157a95146 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -100,17 +100,12 @@ module ActionView # # number_to_currency(-1234567890.50, negative_format: "(%u%n)") # # => ($1,234,567,890.50) - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "") - # # => £1234567890,50 - # number_to_currency(1234567890.50, unit: "£", separator: ",", delimiter: "", format: "%n %u") - # # => 1234567890,50 £ + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "") + # # => R$1234567890,50 + # number_to_currency(1234567890.50, unit: "R$", separator: ",", delimiter: "", format: "%n %u") + # # => 1234567890,50 R$ def number_to_currency(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_currency(number, options) - } + delegate_number_helper_method(:number_to_currency, number, options) end # Formats a +number+ as a percentage string (e.g., 65%). You can @@ -150,12 +145,7 @@ module ActionView # # number_to_percentage("98a", raise: true) # => InvalidNumberError def number_to_percentage(number, options = {}) - return unless number - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_percentage(number, options) - } + delegate_number_helper_method(:number_to_percentage, number, options) end # Formats a +number+ with grouped thousands using +delimiter+ @@ -188,11 +178,7 @@ module ActionView # # number_with_delimiter("112a", raise: true) # => raise InvalidNumberError def number_with_delimiter(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_delimited(number, options) - } + delegate_number_helper_method(:number_to_delimited, number, options) end # Formats a +number+ with the specified level of @@ -237,11 +223,7 @@ module ActionView # number_with_precision(1111.2345, precision: 2, separator: ',', delimiter: '.') # # => 1.111,23 def number_with_precision(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_rounded(number, options) - } + delegate_number_helper_method(:number_to_rounded, number, options) end # Formats the bytes in +number+ into a more understandable @@ -293,11 +275,7 @@ module ActionView # number_to_human_size(1234567890123, precision: 5) # => "1.1229 TB" # number_to_human_size(524288000, precision: 5) # => "500 MB" def number_to_human_size(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) - - wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human_size(number, options) - } + delegate_number_helper_method(:number_to_human_size, number, options) end # Pretty prints (formats and approximates) a number in a way it @@ -399,21 +377,36 @@ module ActionView # number_to_human(0.34, units: :distance) # => "34 centimeters" # def number_to_human(number, options = {}) - options = escape_unsafe_delimiters_and_separators(options.symbolize_keys) + delegate_number_helper_method(:number_to_human, number, options) + end + + private + + def delegate_number_helper_method(method, number, options) + return unless number + options = escape_unsafe_options(options.symbolize_keys) wrap_with_output_safety_handling(number, options.delete(:raise)) { - ActiveSupport::NumberHelper.number_to_human(number, options) + ActiveSupport::NumberHelper.public_send(method, number, options) } end - private - - def escape_unsafe_delimiters_and_separators(options) - options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe? - options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe? + def escape_unsafe_options(options) + options[:format] = ERB::Util.html_escape(options[:format]) if options[:format] + options[:negative_format] = ERB::Util.html_escape(options[:negative_format]) if options[:negative_format] + options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] + options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] + options[:unit] = ERB::Util.html_escape(options[:unit]) if options[:unit] && !options[:unit].html_safe? + options[:units] = escape_units(options[:units]) if options[:units] && Hash === options[:units] options end + def escape_units(units) + Hash[units.map do |k, v| + [k, ERB::Util.html_escape(v)] + end] + end + def wrap_with_output_safety_handling(number, raise_on_invalid, &block) valid_float = valid_float?(number) raise InvalidNumberError, number if raise_on_invalid && !valid_float diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 458086de96..ebfc35a4c7 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -12,6 +12,14 @@ module ActionView # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. # * <tt>:text</tt> - Renders the text passed in out. + # * <tt>:plain</tt> - Renders the text passed in out. Setting the content + # type as <tt>text/plain</tt>. + # * <tt>:html</tt> - Renders the html safe string passed in out, otherwise + # performs html escape on the string first. Setting the content type as + # <tt>text/html</tt>. + # * <tt>:body</tt> - Renders the text passed in, and inherits the content + # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt> + # object. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 732f35643a..a9f3b0ffbc 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -42,7 +42,8 @@ module ActionView # For example, a key +user_id+ would render as <tt>data-user-id</tt> and # thus accessed as <tt>dataset.userId</tt>. # - # Values are encoded to JSON, with the exception of strings and symbols. + # Values are encoded to JSON, with the exception of strings, symbols and + # BigDecimals. # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> # from 1.4.3. # @@ -56,6 +57,9 @@ module ActionView # tag("input", type: 'text', disabled: true) # # => <input type="text" disabled="disabled" /> # + # tag("input", type: 'text', class: ["strong", "highlight"]) + # # => <input class="strong highlight" type="text" /> + # # tag("img", src: "open & shut.png") # # => <img src="open & shut.png" /> # @@ -75,7 +79,7 @@ module ActionView # Set escape to false to disable attribute value escaping. # # ==== Options - # The +options+ hash is used with attributes with no value like (<tt>disabled</tt> and + # The +options+ hash can be used with attributes with no value like (<tt>disabled</tt> and # <tt>readonly</tt>), which you can give a value of true in the +options+ hash. You can use # symbols or strings for the attribute names. # @@ -84,6 +88,8 @@ module ActionView # # => <p>Hello world!</p> # content_tag(:div, content_tag(:p, "Hello world!"), class: "strong") # # => <div class="strong"><p>Hello world!</p></div> + # content_tag(:div, "Hello world!", class: ["strong", "highlight"]) + # # => <div class="strong highlight">Hello world!</div> # content_tag("select", options, multiple: true) # # => <select multiple="multiple">...options...</select> # @@ -114,7 +120,7 @@ module ActionView # cdata_section("hello]]>world") # # => <![CDATA[hello]]]]><![CDATA[>world]]> def cdata_section(content) - splitted = content.gsub(']]>', ']]]]><![CDATA[>') + splitted = content.to_s.gsub(']]>', ']]]]><![CDATA[>') "<![CDATA[#{splitted}]]>".html_safe end @@ -151,7 +157,7 @@ module ActionView attrs << tag_option(key, value, escape) end end - " #{attrs.sort! * ' '}".html_safe unless attrs.empty? + " #{attrs.sort! * ' '}" unless attrs.empty? end def data_tag_option(key, value, escape) diff --git a/actionview/lib/action_view/helpers/tags.rb b/actionview/lib/action_view/helpers/tags.rb index a05e16979a..45c75d10c0 100644 --- a/actionview/lib/action_view/helpers/tags.rb +++ b/actionview/lib/action_view/helpers/tags.rb @@ -3,37 +3,39 @@ module ActionView module Tags #:nodoc: extend ActiveSupport::Autoload - autoload :Base - autoload :CheckBox - autoload :CollectionCheckBoxes - autoload :CollectionRadioButtons - autoload :CollectionSelect - autoload :ColorField - autoload :DateField - autoload :DateSelect - autoload :DatetimeField - autoload :DatetimeLocalField - autoload :DatetimeSelect - autoload :EmailField - autoload :FileField - autoload :GroupedCollectionSelect - autoload :HiddenField - autoload :Label - autoload :MonthField - autoload :NumberField - autoload :PasswordField - autoload :RadioButton - autoload :RangeField - autoload :SearchField - autoload :Select - autoload :TelField - autoload :TextArea - autoload :TextField - autoload :TimeField - autoload :TimeSelect - autoload :TimeZoneSelect - autoload :UrlField - autoload :WeekField + eager_autoload do + autoload :Base + autoload :CheckBox + autoload :CollectionCheckBoxes + autoload :CollectionRadioButtons + autoload :CollectionSelect + autoload :ColorField + autoload :DateField + autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField + autoload :DatetimeSelect + autoload :EmailField + autoload :FileField + autoload :GroupedCollectionSelect + autoload :HiddenField + autoload :Label + autoload :MonthField + autoload :NumberField + autoload :PasswordField + autoload :RadioButton + autoload :RangeField + autoload :SearchField + autoload :Select + autoload :TelField + autoload :TextArea + autoload :TextField + autoload :TimeField + autoload :TimeSelect + autoload :TimeZoneSelect + autoload :UrlField + autoload :WeekField + end end end end diff --git a/actionview/lib/action_view/helpers/tags/collection_helpers.rb b/actionview/lib/action_view/helpers/tags/collection_helpers.rb index 787039c82e..991f32cea2 100644 --- a/actionview/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionview/lib/action_view/helpers/tags/collection_helpers.rb @@ -18,7 +18,7 @@ module ActionView end def label(label_html_options={}, &block) - html_options = label_html_options.merge(@input_html_options) + html_options = @input_html_options.slice(:index, :namespace).merge(label_html_options) @template_object.label(@object_name, @sanitized_attribute_name, @text, html_options, &block) end end diff --git a/actionview/lib/action_view/helpers/tags/label.rb b/actionview/lib/action_view/helpers/tags/label.rb index 180aa9ac27..6335e3dd4d 100644 --- a/actionview/lib/action_view/helpers/tags/label.rb +++ b/actionview/lib/action_view/helpers/tags/label.rb @@ -30,14 +30,13 @@ module ActionView add_default_name_and_id_for_value(tag_value, name_and_id) options.delete("index") options.delete("namespace") - options.delete("multiple") options["for"] = name_and_id["id"] unless options.key?("for") if block_given? content = @template_object.capture(&block) else content = if @content.blank? - @object_name.gsub!(/\[(.*)_attributes\]\[\d\]/, '.\1') + @object_name.gsub!(/\[(.*)_attributes\]\[\d+\]/, '.\1') method_and_value = tag_value.present? ? "#{@method_name}.#{tag_value}" : @method_name if object.respond_to?(:to_model) diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index c23d605c5f..7cfbca5b6f 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -31,6 +31,8 @@ module ActionView include SanitizeHelper include TagHelper + include OutputSafetyHelper + # The preferred method of outputting text in your views is to use the # <%= "text" %> eRuby syntax. The regular _puts_ and _print_ methods # do not operate as expected in an eRuby code block. If you absolutely must @@ -80,6 +82,9 @@ module ActionView # # => "And they f... (continued)" # # truncate("<p>Once upon a time in a world far far away</p>") + # # => "<p>Once upon a time in a wo..." + # + # truncate("<p>Once upon a time in a world far far away</p>", escape: false) # # => "<p>Once upon a time in a wo..." # # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" } @@ -268,7 +273,7 @@ module ActionView content_tag(wrapper_tag, nil, html_options) else paragraphs.map! { |paragraph| - content_tag(wrapper_tag, paragraph, html_options, options[:sanitize]) + content_tag(wrapper_tag, raw(paragraph), html_options) }.join("\n\n").html_safe end end @@ -314,7 +319,7 @@ module ActionView options = values.extract_options! name = options.fetch(:name, 'default') - values.unshift(first_value) + values.unshift(*first_value) cycle = get_cycle(name) unless cycle && cycle.values == values diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb index ad8eb47f1f..0bc40874d9 100644 --- a/actionview/lib/action_view/helpers/translation_helper.rb +++ b/actionview/lib/action_view/helpers/translation_helper.rb @@ -1,24 +1,14 @@ require 'action_view/helpers/tag_helper' require 'i18n/exceptions' -module I18n - class ExceptionHandler - include Module.new { - def call(exception, locale, key, options) - exception.is_a?(MissingTranslation) && options[:rescue_format] == :html ? super.html_safe : super - end - } - end -end - module ActionView # = Action View Translation Helpers module Helpers module TranslationHelper # Delegates to <tt>I18n#translate</tt> but also performs three additional functions. # - # First, it'll pass the <tt>rescue_format: :html</tt> option to I18n so that any - # thrown +MissingTranslation+ messages will be turned into inline spans that + # First, it will ensure that any thrown +MissingTranslation+ messages will be turned + # into inline spans that: # # * have a "translation-missing" class set, # * contain the missing key as a title attribute and @@ -44,8 +34,17 @@ module ActionView # naming convention helps to identify translations that include HTML tags so that # you know what kind of output to expect when you call translate in a template. def translate(key, options = {}) - options.merge!(:rescue_format => :html) unless options.key?(:rescue_format) options[:default] = wrap_translate_defaults(options[:default]) if options[:default] + + # If the user has specified rescue_format then pass it all through, otherwise use + # raise and do the work ourselves + options[:raise] ||= ActionView::Base.raise_on_missing_translations + + raise_error = options[:raise] || options.key?(:rescue_format) + unless raise_error + options[:raise] = true + end + if html_safe_translation_key?(key) html_safe_options = options.dup options.except(*I18n::RESERVED_KEYS).each do |name, value| @@ -59,6 +58,11 @@ module ActionView else I18n.translate(scope_key_by_partial(key), options) end + rescue I18n::MissingTranslationData => e + raise e if raise_error + + keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) + content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}") end alias :t :translate diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index 8b78bd32e0..51379d433f 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -82,7 +82,7 @@ module ActionView # to using GET. If <tt>href: '#'</tt> is used and the user has JavaScript # disabled clicking the link will have no effect. If you are relying on the # POST behavior, you should check for it in your controller's action by using - # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>. + # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>patch?</tt>, or <tt>put?</tt>. # * <tt>remote: true</tt> - This will allow the unobtrusive JavaScript # driver to make an Ajax request to the URL in question instead of following # the link. The drivers each provide mechanisms for listening for the @@ -232,6 +232,11 @@ module ActionView # # <div><input value="New" type="submit" /></div> # # </form>" # + # <%= button_to "New", new_articles_path %> + # # => "<form method="post" action="/articles/new" class="button_to"> + # # <div><input value="New" type="submit" /></div> + # # </form>" + # # <%= button_to [:make_happy, @user] do %> # Make happy <strong><%= @user.name %></strong> # <% end %> @@ -314,8 +319,8 @@ module ActionView inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag) if params - params.each do |name, value| - inner_tags.safe_concat tag(:input, type: "hidden", name: name, value: value.to_param) + params.each do |param_name, value| + inner_tags.safe_concat tag(:input, type: "hidden", name: param_name, value: value.to_param) end end content_tag('form', content_tag('div', inner_tags), form_options) diff --git a/actionview/lib/action_view/layouts.rb b/actionview/lib/action_view/layouts.rb index d8de1d95df..9ee05bd816 100644 --- a/actionview/lib/action_view/layouts.rb +++ b/actionview/lib/action_view/layouts.rb @@ -221,7 +221,7 @@ module ActionView # This module is mixed in if layout conditions are provided. This means # that if no layout conditions are used, this method is not used module LayoutConditions # :nodoc: - private + private # Determines whether the current action has a layout definition by # checking the action name against the :only and :except conditions @@ -269,15 +269,6 @@ module ActionView _write_layout_method end - # If no layout is supplied, look for a template named the return - # value of this method. - # - # ==== Returns - # * <tt>String</tt> - A template name - def _implied_layout_name # :nodoc: - controller_path - end - # Creates a _layout method to be called by _default_layout . # # If a layout is not explicitly mentioned then look for a layout with the controller's name. @@ -335,6 +326,17 @@ module ActionView private :_layout RUBY end + + private + + # If no layout is supplied, look for a template named the return + # value of this method. + # + # ==== Returns + # * <tt>String</tt> - A template name + def _implied_layout_name # :nodoc: + controller_path + end end def _normalize_options(options) # :nodoc: @@ -418,7 +420,7 @@ module ActionView end def _include_layout?(options) - (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) + (options.keys & [:body, :text, :plain, :html, :inline, :partial]).empty? || options.key?(:layout) end end end diff --git a/actionview/lib/action_view/log_subscriber.rb b/actionview/lib/action_view/log_subscriber.rb index 354a136894..6c8d9cb5bf 100644 --- a/actionview/lib/action_view/log_subscriber.rb +++ b/actionview/lib/action_view/log_subscriber.rb @@ -5,7 +5,12 @@ module ActionView # # Provides functionality so that Rails can output logs from Action View. class LogSubscriber < ActiveSupport::LogSubscriber - VIEWS_PATTERN = /^app\/views\//.freeze + VIEWS_PATTERN = /^app\/views\// + + def initialize + @root = nil + super + end def render_template(event) return unless logger.info? @@ -23,8 +28,15 @@ module ActionView protected + EMPTY = '' def from_rails_root(string) - string.sub("#{Rails.root}/", "").sub(VIEWS_PATTERN, "") + string = string.sub(rails_root, EMPTY) + string.sub!(VIEWS_PATTERN, EMPTY) + string + end + + def rails_root + @root ||= "#{Rails.root}/" end end end diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index f9d5b97fe3..855fed0190 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -1,6 +1,7 @@ require 'thread_safe' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/module/attribute_accessors' +require 'action_view/template/resolver' module ActionView # = Action View Lookup Context @@ -52,6 +53,7 @@ module ActionView locales end register_detail(:formats) { ActionView::Base.default_formats || [:html, :text, :js, :css, :xml, :json] } + register_detail(:variants) { [] } register_detail(:handlers){ Template::Handlers.extensions } class DetailsKey #:nodoc: @@ -62,6 +64,13 @@ module ActionView @details_keys = ThreadSafe::Cache.new def self.get(details) + if details[:formats] + details = details.dup + syms = Set.new Mime::SET.symbols + details[:formats] = details[:formats].select { |v| + syms.include? v + } + end @details_keys[details] ||= new end @@ -150,7 +159,14 @@ module ActionView def detail_args_for(options) return @details, details_key if options.empty? # most common path. user_details = @details.merge(options) - [user_details, DetailsKey.get(user_details)] + + if @cache + details_key = DetailsKey.get(user_details) + else + details_key = nil + end + + [user_details, details_key] end # Support legacy foo.erb names even though we now ignore .erb diff --git a/actionview/lib/action_view/railtie.rb b/actionview/lib/action_view/railtie.rb index c2783f6377..81f9c40b85 100644 --- a/actionview/lib/action_view/railtie.rb +++ b/actionview/lib/action_view/railtie.rb @@ -36,19 +36,12 @@ module ActionView end end - initializer "action_view.setup_action_pack", before: :add_view_paths do |app| + initializer "action_view.setup_action_pack" do |app| ActiveSupport.on_load(:action_controller) do - ActionController::Base.superclass.send(:include, ActionView::Layouts) ActionView::RoutingUrlFor.send(:include, ActionDispatch::Routing::UrlFor) end end - initializer "action_view.setup_action_mailer", before: :add_view_paths do |app| - ActiveSupport.on_load(:action_mailer) do - ActionMailer::Base.send(:include, ActionView::Layouts) - end - end - rake_tasks do load "action_view/tasks/dependencies.rake" end diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb index 821026268a..36f17f01fd 100644 --- a/actionview/lib/action_view/renderer/partial_renderer.rb +++ b/actionview/lib/action_view/renderer/partial_renderer.rb @@ -159,7 +159,7 @@ module ActionView # </div> # # If a collection is given, the layout will be rendered once for each item in - # the collection. Just think these two snippets have the same output: + # the collection. For example, these two snippets have the same output: # # <%# app/views/users/_user.html.erb %> # Name: <%= user.name %> diff --git a/actionview/lib/action_view/renderer/streaming_template_renderer.rb b/actionview/lib/action_view/renderer/streaming_template_renderer.rb index 9cf6eb0c65..3ab2cd36fc 100644 --- a/actionview/lib/action_view/renderer/streaming_template_renderer.rb +++ b/actionview/lib/action_view/renderer/streaming_template_renderer.rb @@ -58,7 +58,7 @@ module ActionView def delayed_render(buffer, template, layout, view, locals) # Wrap the given buffer in the StreamingBuffer and pass it to the - # underlying template handler. Now, everytime something is concatenated + # underlying template handler. Now, every time something is concatenated # to the buffer, it is not appended to an array, but streamed straight # to the client. output = ActionView::StreamingBuffer.new(buffer) diff --git a/actionview/lib/action_view/renderer/template_renderer.rb b/actionview/lib/action_view/renderer/template_renderer.rb index 668831dff3..be17097428 100644 --- a/actionview/lib/action_view/renderer/template_renderer.rb +++ b/actionview/lib/action_view/renderer/template_renderer.rb @@ -21,8 +21,14 @@ module ActionView def determine_template(options) #:nodoc: keys = options.fetch(:locals, {}).keys - if options.key?(:text) + if options.key?(:body) + Template::Text.new(options[:body]) + elsif options.key?(:text) Template::Text.new(options[:text], formats.first) + elsif options.key?(:plain) + Template::Text.new(options[:plain]) + elsif options.key?(:html) + Template::HTML.new(options[:html], formats.first) elsif options.key?(:file) with_fallbacks { find_template(options[:file], nil, false, keys, @details) } elsif options.key?(:inline) @@ -35,7 +41,7 @@ module ActionView find_template(options[:template], options[:prefixes], false, keys, @details) end else - raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file or :text option." + raise ArgumentError, "You invoked render but did not give any of :partial, :template, :inline, :file, :plain, :text or :body option." end end diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 18a0788f8e..017302d40f 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -77,13 +77,6 @@ module ActionView @_view_renderer ||= ActionView::Renderer.new(lookup_context) end - # Find and renders a template based on the options given. - # :api: private - def _render_template(options) #:nodoc: - lookup_context.rendered_format = nil if options[:formats] - view_renderer.render(view_context, options) - end - def render_to_body(options = {}) _process_options(options) _render_template(options) @@ -95,8 +88,19 @@ module ActionView private + # Find and render a template based on the options given. + # :api: private + def _render_template(options) #:nodoc: + variant = options[:variant] + + lookup_context.rendered_format = nil if options[:formats] + lookup_context.variants = variant if variant + + view_renderer.render(view_context, options) + end + # Assign the rendered format to lookup context. - def _process_format(format) #:nodoc: + def _process_format(format, options = {}) #:nodoc: super lookup_context.formats = [format.to_sym] lookup_context.rendered_format = lookup_context.formats.first diff --git a/actionview/lib/action_view/routing_url_for.rb b/actionview/lib/action_view/routing_url_for.rb index f10e7e88ba..33be06cbf7 100644 --- a/actionview/lib/action_view/routing_url_for.rb +++ b/actionview/lib/action_view/routing_url_for.rb @@ -83,6 +83,8 @@ module ActionView super when :back _back_url + when Array + polymorphic_path(options, options.extract_options!) else polymorphic_path(options) end diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb index 9b0619f1aa..9d39d02a37 100644 --- a/actionview/lib/action_view/template.rb +++ b/actionview/lib/action_view/template.rb @@ -90,13 +90,14 @@ module ActionView eager_autoload do autoload :Error autoload :Handlers + autoload :HTML autoload :Text autoload :Types end extend Template::Handlers - attr_accessor :locals, :formats, :virtual_path + attr_accessor :locals, :formats, :variants, :virtual_path attr_reader :source, :identifier, :handler, :original_encoding, :updated_at @@ -122,6 +123,7 @@ module ActionView @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.respond_to?(:ref) ? f.ref : f } + @variants = [details[:variant]] @compile_mutex = Mutex.new end diff --git a/actionview/lib/action_view/template/error.rb b/actionview/lib/action_view/template/error.rb index a89d51221e..743ef6de0a 100644 --- a/actionview/lib/action_view/template/error.rb +++ b/actionview/lib/action_view/template/error.rb @@ -41,6 +41,9 @@ module ActionView 'template' end + if partial && path.present? + path = path.sub(%r{([^/]+)$}, "_\\1") + end searched_paths = prefixes.map { |prefix| [prefix, path].join("/") } out = "Missing #{template_type} #{searched_paths.join(", ")} with #{details.inspect}. Searched in:\n" @@ -56,13 +59,13 @@ module ActionView class Error < ActionViewError #:nodoc: SOURCE_CODE_RADIUS = 3 - attr_reader :original_exception, :backtrace + attr_reader :original_exception def initialize(template, original_exception) super(original_exception.message) @template, @original_exception = template, original_exception @sub_templates = nil - @backtrace = original_exception.backtrace + set_backtrace(original_exception.backtrace) end def file_name diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index c8a0059596..4523060442 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -18,7 +18,7 @@ module ActionView src << "@output_buffer.safe_append='" src << "\n" * @newline_pending if @newline_pending > 0 src << escape_text(text) - src << "';" + src << "'.freeze;" @newline_pending = 0 end @@ -67,7 +67,7 @@ module ActionView def flush_newline_if_pending(src) if @newline_pending > 0 - src << "@output_buffer.safe_append='#{"\n" * @newline_pending}';" + src << "@output_buffer.safe_append='#{"\n" * @newline_pending}'.freeze;" @newline_pending = 0 end end diff --git a/actionview/lib/action_view/template/html.rb b/actionview/lib/action_view/template/html.rb new file mode 100644 index 0000000000..0321f819b5 --- /dev/null +++ b/actionview/lib/action_view/template/html.rb @@ -0,0 +1,34 @@ +module ActionView #:nodoc: + # = Action View HTML Template + class Template + class HTML #:nodoc: + attr_accessor :type + + def initialize(string, type = nil) + @string = string.to_s + @type = Types[type] || type if type + @type ||= Types[:html] + end + + def identifier + 'html template' + end + + def inspect + 'html template' + end + + def to_str + ERB::Util.h(@string) + end + + def render(*args) + to_str + end + + def formats + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] + end + end + end +end diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb index 3304605c1a..403824bd8e 100644 --- a/actionview/lib/action_view/template/resolver.rb +++ b/actionview/lib/action_view/template/resolver.rb @@ -1,6 +1,6 @@ require "pathname" require "active_support/core_ext/class" -require "active_support/core_ext/class/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors" require "action_view/template" require "thread" require "thread_safe" @@ -154,7 +154,8 @@ module ActionView cached = nil templates.each do |t| t.locals = locals - t.formats = details[:formats] || [:html] if t.formats.empty? + t.formats = details[:formats] || [:html] if t.formats.empty? + t.variants = details[:variants] || [] if t.variants.empty? t.virtual_path ||= (cached ||= build_path(*path_info)) end end @@ -162,8 +163,8 @@ module ActionView # An abstract class that implements a Resolver with path semantics. class PathResolver < Resolver #:nodoc: - EXTENSIONS = [:locale, :formats, :handlers] - DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{.:handlers,}" + EXTENSIONS = { :locale => ".", :formats => ".", :variants => "+", :handlers => "." } + DEFAULT_PATTERN = ":prefix/:action{.:locale,}{.:formats,}{+:variants,}{.:handlers,}" def initialize(pattern=nil) @pattern = pattern || DEFAULT_PATTERN @@ -189,13 +190,15 @@ module ActionView } template_paths.map { |template| - handler, format = extract_handler_and_format(template, formats) - contents = File.binread template + handler, format, variant = extract_handler_and_format_and_variant(template, formats) + contents = File.binread(template) Template.new(contents, File.expand_path(template), handler, :virtual_path => path.virtual, :format => format, - :updated_at => mtime(template)) + :variant => variant, + :updated_at => mtime(template) + ) } end @@ -228,7 +231,7 @@ module ActionView # Extract handler and formats from path. If a format cannot be a found neither # from the path, or the handler, we should return the array of formats given # to the resolver. - def extract_handler_and_format(path, default_formats) + def extract_handler_and_format_and_variant(path, default_formats) pieces = File.basename(path).split(".") pieces.shift @@ -240,8 +243,10 @@ module ActionView end handler = Template.handler_for_extension(extension) - format = pieces.last && Template::Types[pieces.last] - [handler, format] + format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last + format &&= Template::Types[format] + + [handler, format, variant] end end @@ -303,12 +308,13 @@ module ActionView # An Optimized resolver for Rails' most common case. class OptimizedFileSystemResolver < FileSystemResolver #:nodoc: def build_query(path, details) - exts = EXTENSIONS.map { |ext| details[ext] } query = escape_entry(File.join(@path, path)) - query + exts.map { |ext| - "{#{ext.compact.uniq.map { |e| ".#{e}," }.join}}" - }.join + exts = EXTENSIONS.map do |ext, prefix| + "{#{details[ext].compact.uniq.map { |e| "#{prefix}#{e}," }.join}}" + end.join + + query + exts end end diff --git a/actionview/lib/action_view/template/text.rb b/actionview/lib/action_view/template/text.rb index 859c7bc3ce..04f5b8d17a 100644 --- a/actionview/lib/action_view/template/text.rb +++ b/actionview/lib/action_view/template/text.rb @@ -27,7 +27,7 @@ module ActionView #:nodoc: end def formats - [@type.to_sym] + [@type.respond_to?(:ref) ? @type.ref : @type.to_s] end end end diff --git a/actionview/lib/action_view/template/types.rb b/actionview/lib/action_view/template/types.rb index db77cb5d19..b84e0281ae 100644 --- a/actionview/lib/action_view/template/types.rb +++ b/actionview/lib/action_view/template/types.rb @@ -1,5 +1,5 @@ require 'set' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module ActionView class Template diff --git a/actionview/lib/action_view/testing/resolvers.rb b/actionview/lib/action_view/testing/resolvers.rb index 7afa2fa613..dfb7d463b4 100644 --- a/actionview/lib/action_view/testing/resolvers.rb +++ b/actionview/lib/action_view/testing/resolvers.rb @@ -21,7 +21,7 @@ module ActionView #:nodoc: def query(path, exts, formats) query = "" - EXTENSIONS.each do |ext| + EXTENSIONS.each_key do |ext| query << '(' << exts[ext].map {|e| e && Regexp.escape(".#{e}") }.join('|') << '|)' end query = /^(#{Regexp.escape(path)})#{query}$/ @@ -30,9 +30,13 @@ module ActionView #:nodoc: @hash.each do |_path, array| source, updated_at = array next unless _path =~ query - handler, format = extract_handler_and_format(_path, formats) + handler, format, variant = extract_handler_and_format_and_variant(_path, formats) templates << Template.new(source, _path, handler, - :virtual_path => path.virtual, :format => format, :updated_at => updated_at) + :virtual_path => path.virtual, + :format => format, + :variant => variant, + :updated_at => updated_at + ) end templates.sort_by {|t| -t.identifier.match(/^#{query}$/).captures.reject(&:blank?).size } @@ -41,8 +45,8 @@ module ActionView #:nodoc: class NullResolver < PathResolver def query(path, exts, formats) - handler, format = extract_handler_and_format(path, formats) - [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format)] + handler, format, variant = extract_handler_and_format_and_variant(path, formats) + [ActionView::Template.new("Template generated by Null Resolver", path, handler, :virtual_path => path, :format => format, :variant => variant)] end end diff --git a/actionview/lib/action_view/vendor/html-scanner/html/node.rb b/actionview/lib/action_view/vendor/html-scanner/html/node.rb index 7e7cd4f7b6..27f0f2f6f8 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/node.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/node.rb @@ -71,12 +71,12 @@ module HTML #:nodoc: @line, @position = line, pos end - # Return a textual representation of the node. + # Returns a textual representation of the node. def to_s @children.join() end - # Return false (subclasses must override this to provide specific matching + # Returns false (subclasses must override this to provide specific matching # behavior.) +conditions+ may be of any type. def match(conditions) false diff --git a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb index 30b6b8b141..ed34eecf55 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/sanitizer.rb @@ -1,6 +1,6 @@ require 'set' require 'cgi' -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' module HTML class Sanitizer diff --git a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb index 7f8609c408..dfdd724b9b 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/selector.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/selector.rb @@ -488,7 +488,7 @@ module HTML end - # Return the next element after this one. Skips sibling text nodes. + # Returns the next element after this one. Skips sibling text nodes. # # With the +name+ argument, returns the next element with that name, # skipping other sibling elements. diff --git a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb index 8ac8d34430..adf4e45930 100644 --- a/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb +++ b/actionview/lib/action_view/vendor/html-scanner/html/tokenizer.rb @@ -30,7 +30,7 @@ module HTML #:nodoc: @current_line = 1 end - # Return the next token in the sequence, or +nil+ if there are no more tokens in + # Returns the next token in the sequence, or +nil+ if there are no more tokens in # the stream. def next return nil if @scanner.eos? diff --git a/actionview/lib/action_view/version.rb b/actionview/lib/action_view/version.rb index 094dd474df..f55d3fdaef 100644 --- a/actionview/lib/action_view/version.rb +++ b/actionview/lib/action_view/version.rb @@ -1,11 +1,8 @@ +require_relative 'gem_version' + module ActionView - # Returns the version of the currently loaded ActionView as a Gem::Version + # Returns the version of the currently loaded ActionView as a <tt>Gem::Version</tt> def self.version - Gem::Version.new "4.1.0.beta" - end - - module VERSION #:nodoc: - MAJOR, MINOR, TINY, PRE = ActionView.version.segments - STRING = ActionView.version.to_s + gem_version end end |