aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view
diff options
context:
space:
mode:
authorMikel Lindsaar <raasdnil@gmail.com>2010-03-28 14:44:34 +1100
committerMikel Lindsaar <raasdnil@gmail.com>2010-03-28 14:44:34 +1100
commit2bcc2ebf44b59e46c104c92d621e8051c97bfcf5 (patch)
treed2a3f04fd3020c1b5d88847af62d52f2d5e5bd61 /actionpack/lib/action_view
parentf5774e3e3f70a3acfa559b9ff889e9417fb71d4b (diff)
parent8398f21880a952769ccd6437a4344922fe596dab (diff)
downloadrails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.tar.gz
rails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.tar.bz2
rails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.zip
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'actionpack/lib/action_view')
-rw-r--r--actionpack/lib/action_view/base.rb78
-rw-r--r--actionpack/lib/action_view/context.rb2
-rw-r--r--actionpack/lib/action_view/helpers.rb50
-rw-r--r--actionpack/lib/action_view/helpers/active_model_helper.rb9
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb85
-rw-r--r--actionpack/lib/action_view/helpers/cache_helper.rb23
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/deprecated_block_helpers.rb52
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb78
-rw-r--r--actionpack/lib/action_view/helpers/form_options_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb90
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb402
-rw-r--r--actionpack/lib/action_view/helpers/prototype_helper.rb52
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb16
-rw-r--r--actionpack/lib/action_view/locale/en.yml51
-rw-r--r--actionpack/lib/action_view/lookup_context.rb145
-rw-r--r--actionpack/lib/action_view/railtie.rb12
-rw-r--r--actionpack/lib/action_view/render/layouts.rb26
-rw-r--r--actionpack/lib/action_view/render/partials.rb8
-rw-r--r--actionpack/lib/action_view/render/rendering.rb56
-rw-r--r--actionpack/lib/action_view/template.rb24
-rw-r--r--actionpack/lib/action_view/template/error.rb24
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb62
-rw-r--r--actionpack/lib/action_view/template/handlers/rjs.rb3
-rw-r--r--actionpack/lib/action_view/template/resolver.rb45
-rw-r--r--actionpack/lib/action_view/template/text.rb14
-rw-r--r--actionpack/lib/action_view/test_case.rb29
32 files changed, 855 insertions, 654 deletions
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index ffe3060404..919b1e3470 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -1,27 +1,10 @@
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/array/wrap'
module ActionView #:nodoc:
- class ActionViewError < StandardError #:nodoc:
- end
-
- class MissingTemplate < ActionViewError #:nodoc:
- attr_reader :path
-
- def initialize(paths, path, details, partial)
- @path = path
- display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
- template_type = if partial
- "partial"
- elsif path =~ /layouts/i
- 'layout'
- else
- 'template'
- end
-
- super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
- end
+ class NonConcattingString < ActiveSupport::SafeBuffer
end
# Action View templates can be written in three ways. If the template file has a <tt>.erb</tt> (or <tt>.rhtml</tt>) extension then it uses a mixture of ERb
@@ -176,14 +159,13 @@ module ActionView #:nodoc:
include Helpers, Rendering, Partials, Layouts, ::ERB::Util, Context
extend ActiveSupport::Memoizable
- ActionView.run_base_hooks(self)
-
# Specify whether RJS responses should be wrapped in a try/catch block
# that alert()s the caught exception (and then re-raises it).
cattr_accessor :debug_rjs
@@debug_rjs = false
class_attribute :helpers
+ remove_method :helpers
attr_reader :helpers
class << self
@@ -191,10 +173,12 @@ module ActionView #:nodoc:
delegate :logger, :to => 'ActionController::Base', :allow_nil => true
end
+ ActionView.run_base_hooks(self)
+
attr_accessor :base_path, :assigns, :template_extension, :lookup_context
- attr_internal :captures, :request, :layout, :controller, :template, :config
+ attr_internal :captures, :request, :controller, :template, :config
- delegate :find, :exists?, :formats, :formats=, :locale, :locale=,
+ delegate :find_template, :template_exists?, :formats, :formats=, :locale, :locale=,
:view_paths, :view_paths=, :with_fallbacks, :update_details, :to => :lookup_context
delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers,
@@ -202,42 +186,17 @@ module ActionView #:nodoc:
delegate :logger, :to => :controller, :allow_nil => true
+ # TODO: HACK FOR RJS
+ def view_context
+ self
+ end
+
def self.xss_safe? #:nodoc:
true
end
def self.process_view_paths(value)
- ActionView::PathSet.new(Array(value))
- end
-
- def self.for_controller(controller)
- @views ||= {}
-
- # TODO: Decouple this so helpers are a separate concern in AV just like
- # they are in AC.
- if controller.class.respond_to?(:_helper_serial)
- klass = @views[controller.class._helper_serial] ||= Class.new(self) do
- # Try to make stack traces clearer
- class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def self.name
- "ActionView for #{controller.class}"
- end
-
- def inspect
- "#<#{self.class.name}>"
- end
- ruby_eval
-
- if controller.respond_to?(:_helpers)
- include controller._helpers
- self.helpers = controller._helpers
- end
- end
- else
- klass = self
- end
-
- klass.new(controller.lookup_context, {}, controller)
+ ActionView::PathSet.new(Array.wrap(value))
end
def initialize(lookup_context = nil, assigns_for_first_render = {}, controller = nil, formats = nil) #:nodoc:
@@ -246,7 +205,7 @@ module ActionView #:nodoc:
@helpers = self.class.helpers || Module.new
@_controller = controller
- @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller
+ @_config = ActiveSupport::InheritableOptions.new(controller.config) if controller && controller.respond_to?(:config)
@_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new }
@_virtual_path = nil
@@ -264,14 +223,5 @@ module ActionView #:nodoc:
response.body_parts << part
nil
end
-
- # Evaluates the local assigns and controller ivars, pushes them to the view.
- def _evaluate_assigns_and_ivars #:nodoc:
- if controller
- variables = controller.instance_variable_names
- variables -= controller.protected_instance_variables if controller.respond_to?(:protected_instance_variables)
- variables.each { |name| instance_variable_set(name, controller.instance_variable_get(name)) }
- end
- end
end
end
diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb
index df078a7151..61d2e702a7 100644
--- a/actionpack/lib/action_view/context.rb
+++ b/actionpack/lib/action_view/context.rb
@@ -10,8 +10,6 @@ module ActionView
# In order to work with ActionController, a Context
# must implement:
#
- # Context.for_controller[controller] Create a new ActionView instance for a
- # controller
# Context#render_partial[options]
# - responsible for setting options[:_template]
# - Returns String with the rendered partial
diff --git a/actionpack/lib/action_view/helpers.rb b/actionpack/lib/action_view/helpers.rb
index e359b0bdac..a50c180f63 100644
--- a/actionpack/lib/action_view/helpers.rb
+++ b/actionpack/lib/action_view/helpers.rb
@@ -2,30 +2,32 @@ require 'active_support/benchmarkable'
module ActionView #:nodoc:
module Helpers #:nodoc:
- autoload :ActiveModelHelper, 'action_view/helpers/active_model_helper'
- autoload :AssetTagHelper, 'action_view/helpers/asset_tag_helper'
- autoload :AtomFeedHelper, 'action_view/helpers/atom_feed_helper'
- autoload :CacheHelper, 'action_view/helpers/cache_helper'
- autoload :CaptureHelper, 'action_view/helpers/capture_helper'
- autoload :CsrfHelper, 'action_view/helpers/csrf_helper'
- autoload :DateHelper, 'action_view/helpers/date_helper'
- autoload :DebugHelper, 'action_view/helpers/debug_helper'
- autoload :DeprecatedBlockHelpers, 'action_view/helpers/deprecated_block_helpers'
- autoload :FormHelper, 'action_view/helpers/form_helper'
- autoload :FormOptionsHelper, 'action_view/helpers/form_options_helper'
- autoload :FormTagHelper, 'action_view/helpers/form_tag_helper'
- autoload :JavaScriptHelper, 'action_view/helpers/javascript_helper'
- autoload :NumberHelper, 'action_view/helpers/number_helper'
- autoload :PrototypeHelper, 'action_view/helpers/prototype_helper'
- autoload :RawOutputHelper, 'action_view/helpers/raw_output_helper'
- autoload :RecordIdentificationHelper, 'action_view/helpers/record_identification_helper'
- autoload :RecordTagHelper, 'action_view/helpers/record_tag_helper'
- autoload :SanitizeHelper, 'action_view/helpers/sanitize_helper'
- autoload :ScriptaculousHelper, 'action_view/helpers/scriptaculous_helper'
- autoload :TagHelper, 'action_view/helpers/tag_helper'
- autoload :TextHelper, 'action_view/helpers/text_helper'
- autoload :TranslationHelper, 'action_view/helpers/translation_helper'
- autoload :UrlHelper, 'action_view/helpers/url_helper'
+ extend ActiveSupport::Autoload
+
+ autoload :ActiveModelHelper
+ autoload :AssetTagHelper
+ autoload :AtomFeedHelper
+ autoload :CacheHelper
+ autoload :CaptureHelper
+ autoload :CsrfHelper
+ autoload :DateHelper
+ autoload :DebugHelper
+ autoload :DeprecatedBlockHelpers
+ autoload :FormHelper
+ autoload :FormOptionsHelper
+ autoload :FormTagHelper
+ autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
+ autoload :NumberHelper
+ autoload :PrototypeHelper
+ autoload :RawOutputHelper
+ autoload :RecordIdentificationHelper
+ autoload :RecordTagHelper
+ autoload :SanitizeHelper
+ autoload :ScriptaculousHelper
+ autoload :TagHelper
+ autoload :TextHelper
+ autoload :TranslationHelper
+ autoload :UrlHelper
def self.included(base)
base.extend(ClassMethods)
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb
index 4e12cdab54..80b3d3a664 100644
--- a/actionpack/lib/action_view/helpers/active_model_helper.rb
+++ b/actionpack/lib/action_view/helpers/active_model_helper.rb
@@ -3,6 +3,7 @@ require 'action_view/helpers/form_helper'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/enumerable'
require 'active_support/core_ext/kernel/reporting'
+require 'active_support/core_ext/object/blank'
module ActionView
ActionView.base_hook do
@@ -127,9 +128,9 @@ module ActionView
object = convert_to_model(object)
if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) &&
- (errors = obj.errors[method])
+ (errors = obj.errors[method]).presence
content_tag("div",
- (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]),
+ "#{options[:prepend_text]}#{ERB::Util.h(errors.first)}#{options[:append_text]}".html_safe,
:class => options[:css_class]
)
else
@@ -295,6 +296,10 @@ module ActionView
end
end
+ def error_message
+ object.errors[@method_name]
+ end
+
def column_type
object.send(:column_for_attribute, @method_name).type
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index de3d61ebbe..02ad41719b 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -3,6 +3,7 @@ require 'cgi'
require 'action_view/helpers/url_helper'
require 'action_view/helpers/tag_helper'
require 'active_support/core_ext/file'
+require 'active_support/core_ext/object/blank'
module ActionView
module Helpers #:nodoc:
@@ -11,7 +12,7 @@ module ActionView
# the assets exist before linking to them:
#
# image_tag("rails.png")
- # # => <img alt="Rails src="/images/rails.png?1230601161" />
+ # # => <img alt="Rails" src="/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
# # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
@@ -58,7 +59,7 @@ module ActionView
# +asset_host+ to a proc like this:
#
# ActionController::Base.asset_host = Proc.new { |source|
- # "http://assets#{rand(2) + 1}.example.com"
+ # "http://assets#{source.hash % 2 + 1}.example.com"
# }
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
@@ -66,7 +67,7 @@ module ActionView
# # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
# The example above generates "http://assets1.example.com" and
- # "http://assets2.example.com" randomly. This option is useful for example if
+ # "http://assets2.example.com". This option is useful for example if
# you need fewer/more than four hosts, custom host names, etc.
#
# As you see the proc takes a +source+ parameter. That's a string with the
@@ -242,12 +243,12 @@ module ActionView
# == Caching multiple javascripts into one
#
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
# environment).
#
# ==== Examples
- # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ # javascript_include_tag :all, :cache => true # when config.perform_caching is false =>
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
# <script type="text/javascript" src="/javascripts/effects.js"></script>
# ...
@@ -255,15 +256,15 @@ module ActionView
# <script type="text/javascript" src="/javascripts/shop.js"></script>
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
#
- # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ # javascript_include_tag :all, :cache => true # when config.perform_caching is true =>
# <script type="text/javascript" src="/javascripts/all.js"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false =>
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false =>
# <script type="text/javascript" src="/javascripts/prototype.js"></script>
# <script type="text/javascript" src="/javascripts/cart.js"></script>
# <script type="text/javascript" src="/javascripts/checkout.js"></script>
#
- # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true =>
+ # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true =>
# <script type="text/javascript" src="/javascripts/shop.js"></script>
#
# The <tt>:recursive</tt> option is also available for caching:
@@ -275,11 +276,11 @@ module ActionView
cache = concat || options.delete("cache")
recursive = options.delete("recursive")
- if concat || (ActionController::Base.perform_caching && cache)
+ if concat || (config.perform_caching && cache)
joined_javascript_name = (cache == true ? "all" : cache) + ".js"
joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name)
- unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path)
+ unless config.perform_caching && File.exists?(joined_javascript_path)
write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive))
end
javascript_src_tag(joined_javascript_name, options)
@@ -390,25 +391,25 @@ module ActionView
# == Caching multiple stylesheets into one
#
# You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be
- # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
+ # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching
# is set to true (which is the case by default for the Rails production environment, but not for the development
# environment). Examples:
#
# ==== Examples
- # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
+ # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false =>
# <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" />
#
- # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true =>
+ # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true =>
# <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />
#
- # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false =>
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false =>
# <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" />
# <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" />
#
- # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true =>
+ # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true =>
# <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" />
#
# The <tt>:recursive</tt> option is also available for caching:
@@ -426,11 +427,11 @@ module ActionView
cache = concat || options.delete("cache")
recursive = options.delete("recursive")
- if concat || (ActionController::Base.perform_caching && cache)
+ if concat || (config.perform_caching && cache)
joined_stylesheet_name = (cache == true ? "all" : cache) + ".css"
joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name)
- unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path)
+ unless config.perform_caching && File.exists?(joined_stylesheet_path)
write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive))
end
stylesheet_tag(joined_stylesheet_name, options)
@@ -523,7 +524,7 @@ module ActionView
options.symbolize_keys!
src = options[:src] = path_to_image(source)
- options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize
+ options[:alt] ||= File.basename(src, '.*').capitalize
if size = options.delete(:size)
options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$}
@@ -623,41 +624,37 @@ module ActionView
@@cache_asset_timestamps = true
private
+ def rewrite_extension?(source, dir, ext)
+ source_ext = File.extname(source)[1..-1]
+ ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
+ end
+
+ def rewrite_host_and_protocol(source, has_request)
+ host = compute_asset_host(source)
+ if has_request && host.present? && !is_uri?(host)
+ host = "#{controller.request.protocol}#{host}"
+ end
+ "#{host}#{source}"
+ end
+
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
# roots. Rewrite the asset path for cache-busting asset ids. Include
# asset host, if configured, with the correct request protocol.
def compute_public_path(source, dir, ext = nil, include_host = true)
- has_request = controller.respond_to?(:request)
-
- source_ext = File.extname(source)[1..-1]
- if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}"))))
- source += ".#{ext}"
- end
+ return source if is_uri?(source)
- unless is_uri?(source)
- source = "/#{dir}/#{source}" unless source[0] == ?/
+ source += ".#{ext}" if rewrite_extension?(source, dir, ext)
+ source = "/#{dir}/#{source}" unless source[0] == ?/
+ source = rewrite_asset_path(source)
- source = rewrite_asset_path(source)
-
- if has_request && include_host
- unless source =~ %r{^#{controller.config.relative_url_root}/}
- source = "#{controller.config.relative_url_root}#{source}"
- end
- end
+ has_request = controller.respond_to?(:request)
+ if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/}
+ source = "#{controller.config.relative_url_root}#{source}"
end
+ source = rewrite_host_and_protocol(source, has_request) if include_host
- if include_host && !is_uri?(source)
- host = compute_asset_host(source)
-
- if has_request && !host.blank? && !is_uri?(host)
- host = "#{controller.request.protocol}#{host}"
- end
-
- "#{host}#{source}"
- else
- source
- end
+ source
end
def is_uri?(path)
diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb
index d5cc14b29a..a904af56bb 100644
--- a/actionpack/lib/action_view/helpers/cache_helper.rb
+++ b/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -32,7 +32,28 @@ module ActionView
# <i>Topics listed alphabetically</i>
# <% end %>
def cache(name = {}, options = nil, &block)
- controller.fragment_for(output_buffer, name, options, &block)
+ safe_concat fragment_for(name, options, &block)
+ nil
+ end
+
+ private
+ # TODO: Create an object that has caching read/write on it
+ def fragment_for(name = {}, options = nil, &block) #:nodoc:
+ if controller.perform_caching
+ if controller.fragment_exist?(name, options)
+ controller.read_fragment(name, options)
+ else
+ # VIEW TODO: Make #capture usable outside of ERB
+ # This dance is needed because Builder can't use capture
+ pos = output_buffer.length
+ yield
+ fragment = output_buffer.slice!(pos..-1)
+ controller.write_fragment(name, fragment, options)
+ end
+ else
+ ret = yield
+ ActiveSupport::SafeBuffer.new(ret) if ret.is_a?(String)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb
index 75fc2fddeb..f0be814700 100644
--- a/actionpack/lib/action_view/helpers/capture_helper.rb
+++ b/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -2,22 +2,22 @@ module ActionView
module Helpers
# CaptureHelper exposes methods to let you extract generated markup which
# can be used in other parts of a template or layout file.
- # It provides a method to capture blocks into variables through capture and
+ # It provides a method to capture blocks into variables through capture and
# a way to capture a block of markup for use in a layout through content_for.
module CaptureHelper
- # The capture method allows you to extract part of a template into a
- # variable. You can then use this variable anywhere in your templates or layout.
- #
+ # The capture method allows you to extract part of a template into a
+ # variable. You can then use this variable anywhere in your templates or layout.
+ #
# ==== Examples
# The capture method can be used in ERb templates...
- #
+ #
# <% @greeting = capture do %>
# Welcome to my shiny new web page! The date and time is
# <%= Time.now %>
# <% end %>
#
# ...and Builder (RXML) templates.
- #
+ #
# @timestamp = capture do
# "The current timestamp is #{Time.now}."
# end
@@ -32,16 +32,18 @@ module ActionView
#
def capture(*args)
value = nil
- buffer = with_output_buffer { value = yield *args }
- buffer.presence || value
+ buffer = with_output_buffer { value = yield(*args) }
+ if string = buffer.presence || value and string.is_a?(String)
+ NonConcattingString.new(string)
+ end
end
# Calling content_for stores a block of markup in an identifier for later use.
# You can make subsequent calls to the stored content in other templates or the layout
# by passing the identifier as an argument to <tt>yield</tt>.
- #
+ #
# ==== Examples
- #
+ #
# <% content_for :not_authorized do %>
# alert('You are not authorized to do that!')
# <% end %>
@@ -75,7 +77,7 @@ module ActionView
#
# Then, in another view, you could to do something like this:
#
- # <%= link_to_remote 'Logout', :action => 'logout' %>
+ # <%= link_to 'Logout', :action => 'logout', :remote => true %>
#
# <% content_for :script do %>
# <%= javascript_include_tag :defaults %>
@@ -92,7 +94,7 @@ module ActionView
# <% end %>
#
# <%# Add some other content, or use a different template: %>
- #
+ #
# <% content_for :navigation do %>
# <li><%= link_to 'Login', :action => 'login' %></li>
# <% end %>
@@ -109,13 +111,13 @@ module ActionView
# for elements that will be fragment cached.
def content_for(name, content = nil, &block)
content = capture(&block) if block_given?
- return @_content_for[name] << content if content
- @_content_for[name]
+ @_content_for[name] << content if content
+ @_content_for[name] unless content
end
# content_for? simply checks whether any content has been captured yet using content_for
# Useful to render parts of your layout differently based on what is in your views.
- #
+ #
# ==== Examples
#
# Perhaps you will use different css in you layout if no content_for :right_column
@@ -140,7 +142,7 @@ module ActionView
def with_output_buffer(buf = nil) #:nodoc:
unless buf
buf = ActionView::OutputBuffer.new
- buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding)
+ buf.force_encoding(output_buffer.encoding) if output_buffer && buf.respond_to?(:force_encoding)
end
self.output_buffer, old_buffer = buf, output_buffer
yield
diff --git a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb
deleted file mode 100644
index 3d0657e873..0000000000
--- a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-module ActionView
- module Helpers
- module DeprecatedBlockHelpers
- extend ActiveSupport::Concern
-
- include ActionView::Helpers::TagHelper
- include ActionView::Helpers::TextHelper
- include ActionView::Helpers::JavaScriptHelper
- include ActionView::Helpers::FormHelper
-
- def content_tag(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- def javascript_tag(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- def form_for(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- def form_tag(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- def fields_for(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- def field_set_tag(*, &block)
- block_called_from_erb?(block) ? safe_concat(super) : super
- end
-
- BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template'
-
- if RUBY_VERSION < '1.9.0'
- # Check whether we're called from an erb template.
- # We'd return a string in any other case, but erb <%= ... %>
- # can't take an <% end %> later on, so we have to use <% ... %>
- # and implicitly concat.
- def block_called_from_erb?(block)
- block && eval(BLOCK_CALLED_FROM_ERB, block)
- end
- else
- def block_called_from_erb?(block)
- block && eval(BLOCK_CALLED_FROM_ERB, block.binding)
- end
- end
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 48df26efaa..2ba5339b7d 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -28,7 +28,7 @@ module ActionView
#
# # Note: a @person variable will have been created in the controller.
# # For example: @person = Person.new
- # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
+ # <%= form_for :person, @person, :url => { :action => "create" } do |f| %>
# <%= f.text_field :first_name %>
# <%= f.text_field :last_name %>
# <%= submit_tag 'Create' %>
@@ -44,7 +44,7 @@ module ActionView
#
# If you are using a partial for your form fields, you can use this shortcut:
#
- # <% form_for :person, @person, :url => { :action => "create" } do |f| %>
+ # <%= form_for :person, @person, :url => { :action => "create" } do |f| %>
# <%= render :partial => f %>
# <%= submit_tag 'Create' %>
# <% end %>
@@ -102,7 +102,7 @@ module ActionView
# Rails provides succinct resource-oriented form generation with +form_for+
# like this:
#
- # <% form_for @offer do |f| %>
+ # <%= form_for @offer do |f| %>
# <%= f.label :version, 'Version' %>:
# <%= f.text_field :version %><br />
# <%= f.label :author, 'Author' %>:
@@ -119,7 +119,7 @@ module ActionView
# The generic way to call +form_for+ yields a form builder around a
# model:
#
- # <% form_for :person, :url => { :action => "update" } do |f| %>
+ # <%= form_for :person, :url => { :action => "update" } do |f| %>
# <%= f.error_messages %>
# First name: <%= f.text_field :first_name %><br />
# Last name : <%= f.text_field :last_name %><br />
@@ -143,7 +143,7 @@ module ActionView
# If the instance variable is not <tt>@person</tt> you can pass the actual
# record as the second argument:
#
- # <% form_for :person, person, :url => { :action => "update" } do |f| %>
+ # <%= form_for :person, person, :url => { :action => "update" } do |f| %>
# ...
# <% end %>
#
@@ -175,7 +175,7 @@ module ActionView
# possible to use both the stand-alone FormHelper methods and methods
# from FormTagHelper. For example:
#
- # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ # <%= form_for :person, @person, :url => { :action => "update" } do |f| %>
# First name: <%= f.text_field :first_name %>
# Last name : <%= f.text_field :last_name %>
# Biography : <%= text_area :person, :biography %>
@@ -195,37 +195,37 @@ module ActionView
#
# For example, if <tt>@post</tt> is an existing record you want to edit
#
- # <% form_for @post do |f| %>
+ # <%= form_for @post do |f| %>
# ...
# <% end %>
#
# is equivalent to something like:
#
- # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
+ # <%= form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %>
# ...
# <% end %>
#
# And for new records
#
- # <% form_for(Post.new) do |f| %>
+ # <%= form_for(Post.new) do |f| %>
# ...
# <% end %>
#
# expands to
#
- # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
+ # <%= form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %>
# ...
# <% end %>
#
# You can also overwrite the individual conventions, like this:
#
- # <% form_for(@post, :url => super_post_path(@post)) do |f| %>
+ # <%= form_for(@post, :url => super_post_path(@post)) do |f| %>
# ...
# <% end %>
#
# And for namespaced routes, like +admin_post_url+:
#
- # <% form_for([:admin, @post]) do |f| %>
+ # <%= form_for([:admin, @post]) do |f| %>
# ...
# <% end %>
#
@@ -243,7 +243,7 @@ module ActionView
#
# Example:
#
- # <% form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %>
+ # <%= form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %>
# ...
# <% end %>
#
@@ -263,7 +263,7 @@ module ActionView
# custom builder. For example, let's say you made a helper to
# automatically add labels to form inputs.
#
- # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
+ # <%= form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
# <%= f.text_field :first_name %>
# <%= f.text_field :last_name %>
# <%= text_area :person, :biography %>
@@ -340,11 +340,11 @@ module ActionView
#
# === Generic Examples
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# First name: <%= person_form.text_field :first_name %>
# Last name : <%= person_form.text_field :last_name %>
#
- # <% fields_for @person.permission do |permission_fields| %>
+ # <%= fields_for @person.permission do |permission_fields| %>
# Admin? : <%= permission_fields.check_box :admin %>
# <% end %>
# <% end %>
@@ -352,13 +352,13 @@ module ActionView
# ...or if you have an object that needs to be represented as a different
# parameter, like a Client that acts as a Person:
#
- # <% fields_for :person, @client do |permission_fields| %>
+ # <%= fields_for :person, @client do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
# ...or if you don't have an object, just a name of the parameter:
#
- # <% fields_for :person do |permission_fields| %>
+ # <%= fields_for :person do |permission_fields| %>
# Admin?: <%= permission_fields.check_box :admin %>
# <% end %>
#
@@ -402,9 +402,9 @@ module ActionView
#
# This model can now be used with a nested fields_for, like so:
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
- # <% person_form.fields_for :address do |address_fields| %>
+ # <%= person_form.fields_for :address do |address_fields| %>
# Street : <%= address_fields.text_field :street %>
# Zip code: <%= address_fields.text_field :zip_code %>
# <% end %>
@@ -427,15 +427,15 @@ module ActionView
# accepts_nested_attributes_for :address, :allow_destroy => true
# end
#
- # Now, when you use a form element with the <tt>_delete</tt> parameter,
+ # Now, when you use a form element with the <tt>_destroy</tt> parameter,
# with a value that evaluates to +true+, you will destroy the associated
# model (eg. 1, '1', true, or 'true'):
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
- # <% person_form.fields_for :address do |address_fields| %>
+ # <%= person_form.fields_for :address do |address_fields| %>
# ...
- # Delete: <%= address_fields.check_box :_delete %>
+ # Delete: <%= address_fields.check_box :_destroy %>
# <% end %>
# <% end %>
#
@@ -459,9 +459,9 @@ module ActionView
# the nested fields_for call will be repeated for each instance in the
# collection:
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
- # <% person_form.fields_for :projects do |project_fields| %>
+ # <%= person_form.fields_for :projects do |project_fields| %>
# <% if project_fields.object.active? %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
@@ -470,11 +470,11 @@ module ActionView
#
# It's also possible to specify the instance to be used:
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
# <% @person.projects.each do |project| %>
# <% if project.active? %>
- # <% person_form.fields_for :projects, project do |project_fields| %>
+ # <%= person_form.fields_for :projects, project do |project_fields| %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
# <% end %>
@@ -483,9 +483,9 @@ module ActionView
#
# Or a collection to be used:
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
- # <% person_form.fields_for :projects, @active_projects do |project_fields| %>
+ # <%= person_form.fields_for :projects, @active_projects do |project_fields| %>
# Name: <%= project_fields.text_field :name %>
# <% end %>
# <% end %>
@@ -508,14 +508,14 @@ module ActionView
# end
#
# This will allow you to specify which models to destroy in the
- # attributes hash by adding a form element for the <tt>_delete</tt>
+ # attributes hash by adding a form element for the <tt>_destroy</tt>
# parameter with a value that evaluates to +true+
# (eg. 1, '1', true, or 'true'):
#
- # <% form_for @person, :url => { :action => "update" } do |person_form| %>
+ # <%= form_for @person, :url => { :action => "update" } do |person_form| %>
# ...
- # <% person_form.fields_for :projects do |project_fields| %>
- # Delete: <%= project_fields.check_box :_delete %>
+ # <%= person_form.fields_for :projects do |project_fields| %>
+ # Delete: <%= project_fields.check_box :_destroy %>
# <% end %>
# <% end %>
def fields_for(record_or_name_or_array, *args, &block)
@@ -725,7 +725,7 @@ module ActionView
# Unfortunately that workaround does not work when the check box goes
# within an array-like parameter, as in
#
- # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
+ # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %>
# <%= form.check_box :paid %>
# ...
# <% end %>
@@ -1014,7 +1014,7 @@ module ActionView
class FormBuilder #:nodoc:
# The methods which wrap a form helper call.
class_inheritable_accessor :field_helpers
- self.field_helpers = (FormHelper.instance_methods - ['form_for'])
+ self.field_helpers = (FormHelper.instance_method_names - ['form_for'])
attr_accessor :object_name, :object, :options
@@ -1040,7 +1040,7 @@ module ActionView
end
(field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector|
- src = <<-end_src
+ src, file, line = <<-end_src, __FILE__, __LINE__ + 1
def #{selector}(method, options = {}) # def text_field(method, options = {})
@template.send( # @template.send(
#{selector.inspect}, # "text_field",
@@ -1049,7 +1049,7 @@ module ActionView
objectify_options(options)) # objectify_options(options))
end # end
end_src
- class_eval src, __FILE__, __LINE__
+ class_eval src, file, line
end
def fields_for(record_or_name_or_array, *args, &block)
@@ -1115,7 +1115,7 @@ module ActionView
# Add the submit button for the given form. When no value is given, it checks
# if the object is a new resource or not to create the proper label:
#
- # <% form_for @post do |f| %>
+ # <%= form_for @post do |f| %>
# <%= f.submit %>
# <% end %>
#
diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb
index 7039ecd233..4c523d4b20 100644
--- a/actionpack/lib/action_view/helpers/form_options_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -151,7 +151,7 @@ module ActionView
# end
#
# Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>):
- # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true})
+ # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true)
#
# If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return:
# <select name="post[author_id]">
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index 573733ffea..07694f5ebb 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -37,12 +37,12 @@ module ActionView
# form_tag('/upload', :multipart => true)
# # => <form action="/upload" method="post" enctype="multipart/form-data">
#
- # <% form_tag('/posts')do -%>
+ # <%= form_tag('/posts')do -%>
# <div><%= submit_tag 'Save' %></div>
# <% end -%>
# # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form>
#
- # <% form_tag('/posts', :remote => true) %>
+ # <%= form_tag('/posts', :remote => true) %>
# # => <form action="/posts" method="post" data-remote="true">
#
def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block)
@@ -430,17 +430,17 @@ module ActionView
# <tt>options</tt> accept the same values as tag.
#
# ==== Examples
- # <% field_set_tag do %>
+ # <%= field_set_tag do %>
# <p><%= text_field_tag 'name' %></p>
# <% end %>
# # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset>
#
- # <% field_set_tag 'Your details' do %>
+ # <%= field_set_tag 'Your details' do %>
# <p><%= text_field_tag 'name' %></p>
# <% end %>
# # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset>
#
- # <% field_set_tag nil, :class => 'format' do %>
+ # <%= field_set_tag nil, :class => 'format' do %>
# <p><%= text_field_tag 'name' %></p>
# <% end %>
# # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset>
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb
index 8dab3094dd..b0a7718f22 100644
--- a/actionpack/lib/action_view/helpers/javascript_helper.rb
+++ b/actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -1,5 +1,4 @@
require 'action_view/helpers/tag_helper'
-require 'action_view/helpers/prototype_helper'
module ActionView
module Helpers
@@ -71,7 +70,7 @@ module ActionView
#
# Instead of passing the content as an argument, you can also use a block
# in which case, you pass your +html_options+ as the first parameter.
- # <% javascript_tag :defer => 'defer' do -%>
+ # <%= javascript_tag :defer => 'defer' do -%>
# alert('All is good')
# <% end -%>
def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block)
@@ -89,6 +88,93 @@ module ActionView
def javascript_cdata_section(content) #:nodoc:
"\n//#{cdata_section("\n#{content}\n//")}\n".html_safe
end
+
+ # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
+ # onclick handler.
+ #
+ # The first argument +name+ is used as the button's value or display text.
+ #
+ # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ #
+ # The +function+ argument can be omitted in favor of an +update_page+
+ # block, which evaluates to a string when the template is rendered
+ # (instead of making an Ajax request first).
+ #
+ # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ #
+ # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
+ #
+ # Examples:
+ # button_to_function "Greeting", "alert('Hello world!')"
+ # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
+ # button_to_function "Details" do |page|
+ # page[:details].visual_effect :toggle_slide
+ # end
+ # button_to_function "Details", :class => "details_button" do |page|
+ # page[:details].visual_effect :toggle_slide
+ # end
+ def button_to_function(name, *args, &block)
+ html_options = args.extract_options!.symbolize_keys
+
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
+
+ tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
+ end
+
+ # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the
+ # onclick handler and return false after the fact.
+ #
+ # The first argument +name+ is used as the link text.
+ #
+ # The next arguments are optional and may include the javascript function definition and a hash of html_options.
+ #
+ # The +function+ argument can be omitted in favor of an +update_page+
+ # block, which evaluates to a string when the template is rendered
+ # (instead of making an Ajax request first).
+ #
+ # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
+ #
+ # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
+ #
+ #
+ # Examples:
+ # link_to_function "Greeting", "alert('Hello world!')"
+ # Produces:
+ # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a>
+ #
+ # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()")
+ # Produces:
+ # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#">
+ # <img src="/images/delete.png?" alt="Delete"/>
+ # </a>
+ #
+ # link_to_function("Show me more", nil, :id => "more_link") do |page|
+ # page[:details].visual_effect :toggle_blind
+ # page[:more_link].replace_html "Show me less"
+ # end
+ # Produces:
+ # <a href="#" id="more_link" onclick="try {
+ # $(&quot;details&quot;).visualEffect(&quot;toggle_blind&quot;);
+ # $(&quot;more_link&quot;).update(&quot;Show me less&quot;);
+ # }
+ # catch (e) {
+ # alert('RJS error:\n\n' + e.toString());
+ # alert('$(\&quot;details\&quot;).visualEffect(\&quot;toggle_blind\&quot;);
+ # \n$(\&quot;more_link\&quot;).update(\&quot;Show me less\&quot;);');
+ # throw e
+ # };
+ # return false;">Show me more</a>
+ #
+ def link_to_function(name, *args, &block)
+ html_options = args.extract_options!.symbolize_keys
+
+ function = block_given? ? update_page(&block) : args[0] || ''
+ onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;"
+ href = html_options[:href] || '#'
+
+ content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick))
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 46e41bc406..719b64b940 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -3,10 +3,24 @@ require 'active_support/core_ext/float/rounding'
module ActionView
module Helpers #:nodoc:
+
# Provides methods for converting numbers into formatted strings.
# Methods are provided for phone numbers, currency, percentage,
- # precision, positional notation, and file size.
+ # precision, positional notation, file size and pretty printing.
+ #
+ # Most methods expect a +number+ argument, and will return it
+ # unchanged if can't be converted into a valid number.
module NumberHelper
+
+ # Raised when argument +number+ param given to the helpers is invalid and
+ # the option :raise is set to +true+.
+ class InvalidNumberError < StandardError
+ attr_accessor :number
+ def initialize(number)
+ @number = number
+ end
+ end
+
# Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format
# in the +options+ hash.
#
@@ -30,6 +44,17 @@ module ActionView
def number_to_phone(number, options = {})
return nil if number.nil?
+ begin
+ Float(number)
+ is_number_html_safe = true
+ rescue ArgumentError, TypeError
+ if options[:raise]
+ raise InvalidNumberError, number
+ else
+ is_number_html_safe = number.to_s.html_safe?
+ end
+ end
+
number = number.to_s.strip
options = options.symbolize_keys
area_code = options[:area_code] || nil
@@ -46,7 +71,7 @@ module ActionView
number.starts_with?('-') ? number.slice!(1..-1) : number
end
str << " x #{extension}" unless extension.blank?
- str
+ is_number_html_safe ? str.html_safe : str
end
# Formats a +number+ into a currency string (e.g., $13.65). You can customize the format
@@ -72,38 +97,42 @@ module ActionView
# number_to_currency(1234567890.50, :unit => "&pound;", :separator => ",", :delimiter => "", :format => "%n %u")
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
+ return nil if number.nil?
+
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
- currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
+ currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(currency)
- precision = options[:precision] || defaults[:precision]
- unit = options[:unit] || defaults[:unit]
- separator = options[:separator] || defaults[:separator]
- delimiter = options[:delimiter] || defaults[:delimiter]
- format = options[:format] || defaults[:format]
- separator = '' if precision == 0
+ options = options.reverse_merge(defaults)
- value = number_with_precision(number,
- :precision => precision,
- :delimiter => delimiter,
- :separator => separator)
+ unit = options.delete(:unit)
+ format = options.delete(:format)
- if value
+ begin
+ value = number_with_precision(number, options.merge(:raise => true))
format.gsub(/%n/, value).gsub(/%u/, unit).html_safe
- else
- number
+ rescue InvalidNumberError => e
+ if options[:raise]
+ raise
+ else
+ formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit)
+ e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
+ end
end
+
end
# Formats a +number+ as a percentage string (e.g., 65%). You can customize the
# format in the +options+ hash.
#
# ==== Options
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
#
# ==== Examples
# number_to_percentage(100) # => 100.000%
@@ -111,21 +140,25 @@ module ActionView
# number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000%
# number_to_percentage(302.24398923423, :precision => 5) # => 302.24399%
def number_to_percentage(number, options = {})
+ return nil if number.nil?
+
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
- percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
+ percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(percentage)
- precision = options[:precision] || defaults[:precision]
- separator = options[:separator] || defaults[:separator]
- delimiter = options[:delimiter] || defaults[:delimiter]
+ options = options.reverse_merge(defaults)
- value = number_with_precision(number,
- :precision => precision,
- :separator => separator,
- :delimiter => delimiter)
- value ? value + "%" : number
+ begin
+ "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe
+ rescue InvalidNumberError => e
+ if options[:raise]
+ raise
+ else
+ e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%"
+ end
+ end
end
# Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can
@@ -133,7 +166,7 @@ module ActionView
#
# ==== Options
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ",").
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
#
# ==== Examples
# number_with_delimiter(12345678) # => 12,345,678
@@ -146,148 +179,186 @@ module ActionView
# You can still use <tt>number_with_delimiter</tt> with the old API that accepts the
# +delimiter+ as its optional second and the +separator+ as its
# optional third parameter:
- # number_with_delimiter(12345678, " ") # => 12 345.678
+ # number_with_delimiter(12345678, " ") # => 12 345 678
# number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05
def number_with_delimiter(number, *args)
options = args.extract_options!
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
+ begin
+ Float(number)
+ rescue ArgumentError, TypeError
+ if options[:raise]
+ raise InvalidNumberError, number
+ else
+ return number
+ end
+ end
+
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
'instead of separate delimiter and precision arguments.', caller)
- delimiter = args[0] || defaults[:delimiter]
- separator = args[1] || defaults[:separator]
+ options[:delimiter] ||= args[0] if args[0]
+ options[:separator] ||= args[1] if args[1]
end
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
- separator ||= (options[:separator] || defaults[:separator])
+ options = options.reverse_merge(defaults)
parts = number.to_s.split('.')
- if parts[0]
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
- parts.join(separator)
- else
- number
- end
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
+ parts.join(options[:separator]).html_safe
+
end
- # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2).
+ # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision
+ # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+).
# You can customize the format in the +options+ hash.
#
# ==== Options
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 3).
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +false+)
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +false+)
#
# ==== Examples
- # number_with_precision(111.2345) # => 111.235
- # number_with_precision(111.2345, :precision => 2) # => 111.23
- # number_with_precision(13, :precision => 5) # => 13.00000
- # number_with_precision(389.32314, :precision => 0) # => 389
+ # number_with_precision(111.2345) # => 111.235
+ # number_with_precision(111.2345, :precision => 2) # => 111.23
+ # number_with_precision(13, :precision => 5) # => 13.00000
+ # number_with_precision(389.32314, :precision => 0) # => 389
+ # number_with_precision(111.2345, :significant => true) # => 111
+ # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
+ # number_with_precision(13, :precision => 5, :significant => true) # => 13.000
+ # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true)
+ # # => 13
+ # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
#
# You can still use <tt>number_with_precision</tt> with the old API that accepts the
# +precision+ as its optional second parameter:
- # number_with_precision(number_with_precision(111.2345, 2) # => 111.23
+ # number_with_precision(111.2345, 2) # => 111.23
def number_with_precision(number, *args)
+
options = args.extract_options!
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
- precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
- :raise => true) rescue {}
+ number = begin
+ Float(number)
+ rescue ArgumentError, TypeError
+ if options[:raise]
+ raise InvalidNumberError, number
+ else
+ return number
+ end
+ end
+
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
+ precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(precision_defaults)
+ #Backwards compatibility
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
'instead of a separate precision argument.', caller)
- precision = args[0] || defaults[:precision]
+ options[:precision] ||= args[0] if args[0]
end
- precision ||= (options[:precision] || defaults[:precision])
- separator ||= (options[:separator] || defaults[:separator])
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
+ options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false
+ precision = options.delete :precision
+ significant = options.delete :significant
+ strip_insignificant_zeros = options.delete :strip_insignificant_zeros
- begin
- value = Float(number)
- rescue ArgumentError, TypeError
- value = nil
+ if significant and precision > 0
+ digits = (Math.log10(number) + 1).floor
+ rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision)
+ precision = precision - digits
+ precision = precision > 0 ? precision : 0 #don't let it be negative
+ else
+ rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision
end
-
- if value
- rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision
- number_with_delimiter("%01.#{precision}f" % rounded_number,
- :separator => separator,
- :delimiter => delimiter)
+ formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options)
+ if strip_insignificant_zeros
+ escaped_separator = Regexp.escape(options[:separator])
+ formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe
else
- number
+ formatted_number
end
+
end
STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
- # Formats the bytes in +size+ into a more understandable representation
+ # Formats the bytes in +number+ into a more understandable representation
# (e.g., giving it 1500 yields 1.5 KB). This method is useful for
- # reporting file sizes to users. This method returns nil if
- # +size+ cannot be converted into a number. You can customize the
+ # reporting file sizes to users. You can customize the
# format in the +options+ hash.
#
+ # See <tt>number_to_human</tt> if you want to pretty-print a generic number.
+ #
# ==== Options
- # * <tt>:precision</tt> - Sets the level of precision (defaults to 1).
- # * <tt>:separator</tt> - Sets the separator between the units (defaults to ".").
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
# * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
- #
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
# ==== Examples
# number_to_human_size(123) # => 123 Bytes
- # number_to_human_size(1234) # => 1.2 KB
+ # number_to_human_size(1234) # => 1.21 KB
# number_to_human_size(12345) # => 12.1 KB
- # number_to_human_size(1234567) # => 1.2 MB
- # number_to_human_size(1234567890) # => 1.1 GB
- # number_to_human_size(1234567890123) # => 1.1 TB
- # number_to_human_size(1234567, :precision => 2) # => 1.18 MB
- # number_to_human_size(483989, :precision => 0) # => 473 KB
- # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB
+ # number_to_human_size(1234567) # => 1.18 MB
+ # number_to_human_size(1234567890) # => 1.15 GB
+ # number_to_human_size(1234567890123) # => 1.12 TB
+ # number_to_human_size(1234567, :precision => 2) # => 1.2 MB
+ # number_to_human_size(483989, :precision => 2) # => 470 KB
+ # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB
#
- # Zeros after the decimal point are always stripped out, regardless of the
- # specified precision:
- # helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB"
- # helper.number_to_human_size(524288000, :precision=>5) # => "500 MB"
+ # Unsignificant zeros after the fractional separator are stripped out by default (set
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
+ # number_to_human_size(524288000, :precision=>5) # => "500 MB"
#
# You can still use <tt>number_to_human_size</tt> with the old API that accepts the
# +precision+ as its optional second parameter:
- # number_to_human_size(1234567, 2) # => 1.18 MB
- # number_to_human_size(483989, 0) # => 473 KB
+ # number_to_human_size(1234567, 1) # => 1 MB
+ # number_to_human_size(483989, 2) # => 470 KB
def number_to_human_size(number, *args)
- return nil if number.nil?
-
options = args.extract_options!
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
- human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
+ number = begin
+ Float(number)
+ rescue ArgumentError, TypeError
+ if options[:raise]
+ raise InvalidNumberError, number
+ else
+ return number
+ end
+ end
+
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
+ human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
defaults = defaults.merge(human)
unless args.empty?
ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' +
'instead of a separate precision argument.', caller)
- precision = args[0] || defaults[:precision]
+ options[:precision] ||= args[0] if args[0]
end
- precision ||= (options[:precision] || defaults[:precision])
- separator ||= (options[:separator] || defaults[:separator])
- delimiter ||= (options[:delimiter] || defaults[:delimiter])
+ options = options.reverse_merge(defaults)
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
if number.to_i < 1024
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true)
- storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit)
+ storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe
else
max_exp = STORAGE_UNITS.size - 1
- number = Float(number)
exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024
exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
number /= 1024 ** exponent
@@ -295,15 +366,138 @@ module ActionView
unit_key = STORAGE_UNITS[exponent]
unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
- escaped_separator = Regexp.escape(separator)
- formatted_number = number_with_precision(number,
- :precision => precision,
- :separator => separator,
- :delimiter => delimiter
- ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '')
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit)
+ formatted_number = number_with_precision(number, options)
+ storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
+ end
+ end
+
+ DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion,
+ -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze
+
+ # Pretty prints (formats and approximates) a number in a way it is more readable by humans
+ # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that
+ # can get very large (and too hard to read).
+ #
+ # See <tt>number_to_human_size</tt> if you want to print a file size.
+ #
+ # You can also define you own unit-quantifier names if you want to use other decimal units
+ # (eg.: 1500 becomes "1.5 kilometers", 0.150 becomes "150 mililiters", etc). You may define
+ # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc).
+ #
+ # ==== Options
+ # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3).
+ # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+)
+ # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to "").
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+)
+ # * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys:
+ # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt>
+ # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt>
+ # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are:
+ #
+ # %u The quantifier (ex.: 'thousand')
+ # %n The number
+ #
+ # ==== Examples
+ # number_to_human(123) # => "123"
+ # number_to_human(1234) # => "1.23 Thousand"
+ # number_to_human(12345) # => "12.3 Thousand"
+ # number_to_human(1234567) # => "1.23 Million"
+ # number_to_human(1234567890) # => "1.23 Billion"
+ # number_to_human(1234567890123) # => "1.23 Trillion"
+ # number_to_human(1234567890123456) # => "1.23 Quadrillion"
+ # number_to_human(1234567890123456789) # => "1230 Quadrillion"
+ # number_to_human(489939, :precision => 2) # => "490 Thousand"
+ # number_to_human(489939, :precision => 4) # => "489.9 Thousand"
+ # number_to_human(1234567, :precision => 4,
+ # :significant => false) # => "1.2346 Million"
+ # number_to_human(1234567, :precision => 1,
+ # :separator => ',',
+ # :significant => false) # => "1,2 Million"
+ #
+ # Unsignificant zeros after the decimal separator are stripped out by default (set
+ # <tt>:strip_insignificant_zeros</tt> to +false+ to change that):
+ # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion"
+ # number_to_human(500000000, :precision=>5) # => "500 Million"
+ #
+ # ==== Custom Unit Quantifiers
+ #
+ # You can also use your own custom unit quantifiers:
+ # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt"
+ #
+ # If in your I18n locale you have:
+ # distance:
+ # centi:
+ # one: "centimeter"
+ # other: "centimeters"
+ # unit:
+ # one: "meter"
+ # other: "meters"
+ # thousand:
+ # one: "kilometer"
+ # other: "kilometers"
+ # billion: "gazilion-distance"
+ #
+ # Then you could do:
+ #
+ # number_to_human(543934, :units => :distance) # => "544 kilometers"
+ # number_to_human(54393498, :units => :distance) # => "54400 kilometers"
+ # number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance"
+ # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters"
+ # number_to_human(1, :units => :distance) # => "1 meter"
+ # number_to_human(0.34, :units => :distance) # => "34 centimeters"
+ #
+ def number_to_human(number, options = {})
+ options.symbolize_keys!
+
+ number = begin
+ Float(number)
+ rescue ArgumentError, TypeError
+ if options[:raise]
+ raise InvalidNumberError, number
+ else
+ return number
+ end
end
+
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {})
+ human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {})
+ defaults = defaults.merge(human)
+
+ options = options.reverse_merge(defaults)
+ #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
+ options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros)
+
+ units = options.delete :units
+ unit_exponents = case units
+ when Hash
+ units
+ when String, Symbol
+ I18n.translate(:"#{units}", :locale => options[:locale], :raise => true)
+ when nil
+ I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true)
+ else
+ raise ArgumentError, ":units must be a Hash or String translation scope."
+ end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e}
+
+ number_exponent = Math.log10(number).floor
+ display_exponent = unit_exponents.find{|e| number_exponent >= e }
+ number /= 10 ** display_exponent
+
+ unit = case units
+ when Hash
+ units[DECIMAL_UNITS[display_exponent]]
+ when String, Symbol
+ I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ else
+ I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ end
+
+ decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u")
+ formatted_number = number_with_precision(number, options)
+ decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe
end
+
end
end
end
diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb
index be49b5cc28..ccdc8181db 100644
--- a/actionpack/lib/action_view/helpers/prototype_helper.rb
+++ b/actionpack/lib/action_view/helpers/prototype_helper.rb
@@ -35,7 +35,7 @@ module ActionView
#
# ...through a form...
#
- # <% form_remote_tag :url => '/shipping' do -%>
+ # <%= form_remote_tag :url => '/shipping' do -%>
# <div><%= submit_tag 'Recalculate Shipping' %></div>
# <% end -%>
#
@@ -102,39 +102,6 @@ module ActionView
:form, :with, :update, :script, :type ]).merge(CALLBACKS)
end
- # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the
- # onclick handler.
- #
- # The first argument +name+ is used as the button's value or display text.
- #
- # The next arguments are optional and may include the javascript function definition and a hash of html_options.
- #
- # The +function+ argument can be omitted in favor of an +update_page+
- # block, which evaluates to a string when the template is rendered
- # (instead of making an Ajax request first).
- #
- # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button"
- #
- # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil
- #
- # Examples:
- # button_to_function "Greeting", "alert('Hello world!')"
- # button_to_function "Delete", "if (confirm('Really?')) do_delete()"
- # button_to_function "Details" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- # button_to_function "Details", :class => "details_button" do |page|
- # page[:details].visual_effect :toggle_slide
- # end
- def button_to_function(name, *args, &block)
- html_options = args.extract_options!.symbolize_keys
-
- function = block_given? ? update_page(&block) : args[0] || ''
- onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};"
-
- tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick))
- end
-
# Returns the JavaScript needed for a remote function.
# Takes the same arguments as link_to_remote.
#
@@ -180,13 +147,10 @@ module ActionView
# #include_helpers_from_context has nothing to overwrite.
class JavaScriptGenerator #:nodoc:
def initialize(context, &block) #:nodoc:
- context._evaluate_assigns_and_ivars
@context, @lines = context, []
- @context.update_details(:formats => [:js, :html]) do
- include_helpers_from_context
- @context.with_output_buffer(@lines) do
- @context.instance_exec(self, &block)
- end
+ include_helpers_from_context
+ @context.with_output_buffer(@lines) do
+ @context.instance_exec(self, &block)
end
end
@@ -616,7 +580,7 @@ module ActionView
# page.hide 'spinner'
# end
def update_page(&block)
- JavaScriptGenerator.new(@template, &block).to_s.html_safe
+ JavaScriptGenerator.new(view_context, &block).to_s.html_safe
end
# Works like update_page but wraps the generated JavaScript in a <script>
@@ -690,6 +654,10 @@ module ActionView
@generator << root if root
end
+ def is_a?(klass)
+ klass == JavaScriptProxy
+ end
+
private
def method_missing(method, *arguments, &block)
if method.to_s =~ /(.*)=$/
@@ -883,5 +851,3 @@ module ActionView
end
end
end
-
-require 'action_view/helpers/javascript_helper'
diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb
index 31411dc08a..a9cf15f418 100644
--- a/actionpack/lib/action_view/helpers/record_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb
@@ -4,7 +4,7 @@ module ActionView
# Produces a wrapper DIV element with id and class parameters that
# relate to the specified Active Record object. Usage example:
#
- # <% div_for(@person, :class => "foo") do %>
+ # <%= div_for(@person, :class => "foo") do %>
# <%=h @person.name %>
# <% end %>
#
@@ -19,7 +19,7 @@ module ActionView
# content_tag_for creates an HTML element with id and class parameters
# that relate to the specified Active Record object. For example:
#
- # <% content_tag_for(:tr, @person) do %>
+ # <%= content_tag_for(:tr, @person) do %>
# <td><%=h @person.first_name %></td>
# <td><%=h @person.last_name %></td>
# <% end %>
@@ -31,7 +31,7 @@ module ActionView
#
# If you require the HTML id attribute to have a prefix, you can specify it:
#
- # <% content_tag_for(:tr, @person, :foo) do %> ...
+ # <%= content_tag_for(:tr, @person, :foo) do %> ...
#
# produces:
#
@@ -41,7 +41,7 @@ module ActionView
# additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined
# with the default class name for your object. For example:
#
- # <% content_tag_for(:li, @person, :class => "bar") %>...
+ # <%= content_tag_for(:li, @person, :class => "bar") %>...
#
# produces:
#
diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb
index d9d2588a2a..9b4cacd4d7 100644
--- a/actionpack/lib/action_view/helpers/tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/tag_helper.rb
@@ -65,7 +65,7 @@ module ActionView
# content_tag("select", options, :multiple => true)
# # => <select multiple="multiple">...options...</select>
#
- # <% content_tag :div, :class => "strong" do -%>
+ # <%= content_tag :div, :class => "strong" do -%>
# Hello world!
# <% end -%>
# # => <div class="strong">Hello world!</div>
@@ -109,7 +109,7 @@ module ActionView
def content_tag_string(name, content, options, escape = true)
tag_options = tag_options(options, escape) if options
- ("<#{name}#{tag_options}>".html_safe << content.to_s).safe_concat("</#{name}>")
+ "<#{name}#{tag_options}>#{ERB::Util.h(content)}</#{name}>".html_safe
end
def tag_options(options, escape = true)
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index b19a9754f4..27be1690dd 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -17,7 +17,7 @@ module ActionView
# concat "hello"
# # is the equivalent of <%= "hello" %>
#
- # if (logged_in == true):
+ # if logged_in
# concat "Logged in!"
# else
# concat link_to('login', :action => login)
@@ -29,7 +29,7 @@ module ActionView
end
def safe_concat(string)
- output_buffer.safe_concat(string)
+ output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string)
end
# Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt>
@@ -415,7 +415,7 @@ module ActionView
# {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'},
# {:first => 'June', :middle => 'Dae', :last => 'Jones'}]
# <% @items.each do |item| %>
- # <tr class="<%= cycle("even", "odd", :name => "row_class") -%>">
+ # <tr class="<%= cycle("odd", "even", :name => "row_class") -%>">
# <td>
# <% item.values.each do |value| %>
# <%# Create a named cycle "colors" %>
@@ -576,7 +576,7 @@ module ActionView
# each email is yielded and the result is used as the link text.
def auto_link_email_addresses(text, html_options = {})
body = text.dup
- text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
+ text.gsub(/([\w\.!#\$%\-+]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do
text = $1
if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/)
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 8a89ee58a0..457944dbb6 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -13,7 +13,7 @@ module ActionView
def translate(key, options = {})
options[:raise] = true
translation = I18n.translate(scope_key_by_partial(key), options)
- translation.is_a?(Array) ? translation.map { |entry| entry.html_safe } : translation.html_safe
+ (translation.respond_to?(:join) ? translation.join : translation).html_safe
rescue I18n::MissingTranslationData => e
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
content_tag('span', keys.join(', '), :class => 'translation_missing')
@@ -29,9 +29,10 @@ module ActionView
private
def scope_key_by_partial(key)
- if (key.respond_to?(:join) ? key.join : key.to_s).first == "."
+ strkey = key.respond_to?(:join) ? key.join : key.to_s
+ if strkey.first == "."
if @_virtual_path
- @_virtual_path.gsub(%r{/_?}, ".") + key.to_s
+ @_virtual_path.gsub(%r{/_?}, ".") + strkey
else
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 148f2868e9..b23d5fcb68 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -1,6 +1,7 @@
require 'action_view/helpers/javascript_helper'
require 'active_support/core_ext/array/access'
require 'active_support/core_ext/hash/keys'
+require 'action_dispatch'
module ActionView
module Helpers #:nodoc:
@@ -9,6 +10,9 @@ module ActionView
# This allows you to use the same format for links in views
# and controllers.
module UrlHelper
+ extend ActiveSupport::Concern
+
+ include ActionDispatch::Routing::UrlFor
include JavaScriptHelper
# Need to map default url options to controller one.
@@ -16,6 +20,10 @@ module ActionView
controller.send(:default_url_options, *args)
end
+ def url_options
+ controller.url_options
+ end
+
# 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
@@ -162,7 +170,7 @@ module ActionView
#
# You can use a block as well if your link target is hard to fit into the name parameter. ERb example:
#
- # <% link_to(@profile) do %>
+ # <%= link_to(@profile) do %>
# <strong><%= @profile.name %></strong> -- <span>Check it out!</span>
# <% end %>
# # => <a href="/profiles/1">
@@ -206,7 +214,7 @@ module ActionView
if block_given?
options = args.first || {}
html_options = args.second
- safe_concat(link_to(capture(&block), options, html_options))
+ link_to(capture(&block), options, html_options)
else
name = args[0]
options = args[1] || {}
@@ -224,7 +232,7 @@ module ActionView
end
href_attr = "href=\"#{url}\"" unless href
- ("<a #{href_attr}#{tag_options}>".html_safe << (name || url)).safe_concat("</a>")
+ "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe
end
end
@@ -578,8 +586,6 @@ module ActionView
add_confirm_to_attributes!(html_options, confirm) if confirm
add_method_to_attributes!(html_options, method) if method
- html_options["data-url"] = options[:url] if options.is_a?(Hash) && options[:url]
-
html_options
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index a3548051c1..a3e2230f6f 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -9,6 +9,11 @@
delimiter: ","
# Number of decimals, behind the separator (the number 1 with a precision of 2 gives: 1.00)
precision: 3
+ # If set to true, precision will mean the number of significant digits instead
+ # of the number of decimal digits (1234 with precision 2 becomes 1200, 1.23543 becomes 1.2)
+ significant: false
+ # If set, the zeros after the decimal separator will always be stripped (eg.: 1.200 will be 1.2)
+ strip_insignificant_zeros: false
# Used in number_to_currency()
currency:
@@ -16,34 +21,43 @@
# Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
format: "%u%n"
unit: "$"
- # These three are to override number.format and are optional
+ # These five are to override number.format and are optional
separator: "."
delimiter: ","
precision: 2
+ significant: false
+ strip_insignificant_zeros: false
# Used in number_to_percentage()
percentage:
format:
- # These three are to override number.format and are optional
+ # These five are to override number.format and are optional
# separator:
delimiter: ""
# precision:
+ # significant: false
+ # strip_insignificant_zeros: false
# Used in number_to_precision()
precision:
format:
- # These three are to override number.format and are optional
+ # These five are to override number.format and are optional
# separator:
delimiter: ""
# precision:
+ # significant: false
+ # strip_insignificant_zeros: false
- # Used in number_to_human_size()
+ # Used in number_to_human_size() and number_to_human()
human:
format:
- # These three are to override number.format and are optional
+ # These five are to override number.format and are optional
# separator:
delimiter: ""
- precision: 1
+ precision: 3
+ significant: true
+ strip_insignificant_zeros: true
+ # Used in number_to_human_size()
storage_units:
# Storage units output formatting.
# %u is the storage unit, %n is the number (default: 2 MB)
@@ -56,6 +70,31 @@
mb: "MB"
gb: "GB"
tb: "TB"
+ # Used in number_to_human()
+ decimal_units:
+ format: "%n %u"
+ # Decimal units output formatting
+ # By default we will only quantify some of the exponents
+ # but the commented ones might be defined or overridden
+ # by the user.
+ units:
+ # femto: Quadrillionth
+ # pico: Trillionth
+ # nano: Billionth
+ # micro: Millionth
+ # mili: Thousandth
+ # centi: Hundredth
+ # deci: Tenth
+ unit: ""
+ # ten:
+ # one: Ten
+ # other: Tens
+ # hundred: Hundred
+ thousand: Thousand
+ million: Million
+ billion: Billion
+ trillion: Trillion
+ quadrillion: Quadrillion
# Used in distance_of_time_in_words(), distance_of_time_in_words_to_now(), time_ago_in_words()
datetime:
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index 91885c7370..9b59aac0eb 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/object/try'
+require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/object/blank'
module ActionView
@@ -11,38 +11,56 @@ module ActionView
@@fallbacks = [FileSystemResolver.new(""), FileSystemResolver.new("/")]
mattr_accessor :registered_details
- self.registered_details = {}
+ self.registered_details = []
+
+ def self.register_detail(name, options = {}, &block)
+ self.registered_details << name
+ Accessors.send :define_method, :"_#{name}_defaults", &block
+ Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1
+ def #{name}
+ @details[:#{name}]
+ end
- def self.register_detail(name, options = {})
- registered_details[name] = lambda do |value|
- value = Array(value.presence || yield)
- value |= [nil] unless options[:allow_nil] == false
- value
- end
+ def #{name}=(value)
+ value = Array.wrap(value.presence || _#{name}_defaults)
+
+ if value != @details[:#{name}]
+ @details_key = nil
+ @details = @details.dup if @details.frozen?
+ @details[:#{name}] = value.freeze
+ end
+ end
+ METHOD
+ end
+
+ # Holds accessors for the registered details.
+ module Accessors #:nodoc:
end
register_detail(:formats) { Mime::SET.symbols }
register_detail(:locale) { [I18n.locale] }
class DetailsKey #:nodoc:
- attr_reader :details
alias :eql? :equal?
+ alias :object_hash :hash
+ attr_reader :hash
@details_keys = Hash.new
def self.get(details)
- @details_keys[details] ||= new(details)
+ @details_keys[details.freeze] ||= new
end
- def initialize(details)
- @details, @hash = details, details.hash
+ def initialize
+ @hash = object_hash
end
end
def initialize(view_paths, details = {})
- @details_key = nil
+ @details, @details_key = { :handlers => default_handlers }, nil
+ @frozen_formats = false
self.view_paths = view_paths
- self.details = details
+ self.update_details(details, true)
end
module ViewPaths
@@ -55,18 +73,20 @@ module ActionView
end
def find(name, prefix = nil, partial = false)
- @view_paths.find(name, prefix, partial || false, details, details_key)
+ @view_paths.find(*args_for_lookup(name, prefix, partial))
end
+ alias :find_template :find
def find_all(name, prefix = nil, partial = false)
- @view_paths.find_all(name, prefix, partial || false, details, details_key)
+ @view_paths.find_all(*args_for_lookup(name, prefix, partial))
end
def exists?(name, prefix = nil, partial = false)
- @view_paths.exists?(name, prefix, partial || false, details, details_key)
+ @view_paths.exists?(*args_for_lookup(name, prefix, partial))
end
+ alias :template_exists? :exists?
- # Add fallbacks to the view paths. Useful in cases you are rendering a file.
+ # Add fallbacks to the view paths. Useful in cases you are rendering a :file.
def with_fallbacks
added_resolvers = 0
self.class.fallbacks.each do |resolver|
@@ -78,73 +98,92 @@ module ActionView
ensure
added_resolvers.times { view_paths.pop }
end
- end
- module Details
- attr_reader :details
+ protected
+
+ def args_for_lookup(name, prefix, partial) #:nodoc:
+ name, prefix = normalize_name(name, prefix)
+ [name, prefix, partial || false, @details, details_key]
+ end
+
+ # Support legacy foo.erb names even though we now ignore .erb
+ # as well as incorrectly putting part of the path in the template
+ # name instead of the prefix.
+ def normalize_name(name, prefix) #:nodoc:
+ name = name.to_s.gsub(handlers_regexp, '')
+ parts = name.split('/')
+ return parts.pop, [prefix, *parts].compact.join("/")
+ end
+
+ def default_handlers #:nodoc:
+ @default_handlers ||= Template::Handlers.extensions
+ end
- def details=(details)
- @details = normalize_details(details)
- @details_key = nil if @details_key && @details_key.details != @details
+ def handlers_regexp #:nodoc:
+ @handlers_regexp ||= /\.(?:#{default_handlers.join('|')})$/
end
+ end
- def details_key
+ module Details
+ # Calculate the details key. Remove the handlers from calculation to improve performance
+ # since the user cannot modify it explicitly.
+ def details_key #:nodoc:
@details_key ||= DetailsKey.get(@details)
end
- # Shortcut to read formats from details.
- def formats
- @details[:formats].compact
+ # Freeze the current formats in the lookup context. By freezing them, you are guaranteeing
+ # that next template lookups are not going to modify the formats. The controller can also
+ # use this, to ensure that formats won't be further modified (as it does in respond_to blocks).
+ def freeze_formats(formats, unless_frozen=false) #:nodoc:
+ return if unless_frozen && @frozen_formats
+ self.formats = formats
+ @frozen_formats = true
end
- # Shortcut to set formats in details.
+ # Overload formats= to reject [:"*/*"] values.
def formats=(value)
- self.details = @details.merge(:formats => value)
+ value = nil if value == [:"*/*"]
+ value << :html if value == [:js]
+ super(value)
end
- # Shortcut to read locale.
+ # Overload locale to return a symbol instead of array
def locale
- I18n.locale
+ @details[:locale].first
end
- # Shortcut to set locale in details and I18n.
+ # Overload locale= to also set the I18n.locale. If the current I18n.config object responds
+ # to i18n_config, it means that it's has a copy of the original I18n configuration and it's
+ # acting as proxy, which we need to skip.
def locale=(value)
- I18n.locale = value
-
- unless I18n.config.respond_to?(:lookup_context)
- self.details = @details.merge(:locale => value)
+ if value
+ config = I18n.config.respond_to?(:i18n_config) ? I18n.config.i18n_config : I18n.config
+ config.locale = value
end
+ super(I18n.locale)
end
# Update the details keys by merging the given hash into the current
# details hash. If a block is given, the details are modified just during
# the execution of the block and reverted to the previous value after.
- def update_details(new_details)
- old_details = @details
- self.details = old_details.merge(new_details)
+ def update_details(new_details, force=false)
+ old_details = @details.dup
+
+ registered_details.each do |key|
+ send(:"#{key}=", new_details[key]) if force || new_details.key?(key)
+ end
if block_given?
begin
yield
ensure
- self.details = old_details
+ @details = old_details
end
end
end
-
- protected
-
- def normalize_details(details)
- details = details.dup
- # TODO: Refactor this concern out of the resolver
- details.delete(:formats) if details[:formats] == [:"*/*"]
- self.class.registered_details.each do |k, v|
- details[k] = v.call(details[k])
- end
- details.freeze
- end
end
+ include Accessors
include Details
include ViewPaths
end
diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb
index 2e5d115630..9cf007cd2b 100644
--- a/actionpack/lib/action_view/railtie.rb
+++ b/actionpack/lib/action_view/railtie.rb
@@ -3,10 +3,10 @@ require "rails"
module ActionView
class Railtie < Rails::Railtie
- railtie_name :action_view
+ config.action_view = ActiveSupport::OrderedOptions.new
require "action_view/railties/log_subscriber"
- log_subscriber ActionView::Railties::LogSubscriber.new
+ log_subscriber :action_view, ActionView::Railties::LogSubscriber.new
initializer "action_view.cache_asset_timestamps" do |app|
unless app.config.cache_classes
@@ -15,5 +15,13 @@ module ActionView
end
end
end
+
+ initializer "action_view.set_configs" do |app|
+ ActionView.base_hook do
+ app.config.action_view.each do |k,v|
+ send "#{k}=", v
+ end
+ end
+ end
end
end \ No newline at end of file
diff --git a/actionpack/lib/action_view/render/layouts.rb b/actionpack/lib/action_view/render/layouts.rb
index 8688de3d18..578f39d817 100644
--- a/actionpack/lib/action_view/render/layouts.rb
+++ b/actionpack/lib/action_view/render/layouts.rb
@@ -1,8 +1,5 @@
-require 'active_support/core_ext/object/try'
-
module ActionView
module Layouts
-
# You can think of a layout as a method that is called with a block. _layout_for
# returns the contents that are yielded to the layout. If the user calls yield
# :some_name, the block, by default, returns content_for(:some_name). If the user
@@ -13,7 +10,7 @@ module ActionView
# ==== Example
#
# # The template
- # <% render :layout => "my_layout" do %>Content<% end %>
+ # <%= render :layout => "my_layout" do %>Content<% end %>
#
# # The layout
# <html><% yield %></html>
@@ -27,7 +24,7 @@ module ActionView
# ==== Example
#
# # The template
- # <% render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %>
+ # <%= render :layout => "my_layout" do |customer| %>Hello <%= customer.name %><% end %>
#
# # The layout
# <html><% yield Struct.new(:name).new("David") %></html>
@@ -46,17 +43,28 @@ module ActionView
# This is the method which actually finds the layout using details in the lookup
# context object. If no layout is found, it checkes if at least a layout with
# the given name exists across all details before raising the error.
- def _find_layout(layout) #:nodoc:
+ #
+ # If self.formats contains several formats, just the first one is considered in
+ # the layout lookup.
+ def find_layout(layout)
begin
- layout =~ /^\// ?
- with_fallbacks { find(layout) } : find(layout)
+ if formats.size == 1
+ _find_layout(layout)
+ else
+ update_details(:formats => self.formats.first){ _find_layout(layout) }
+ end
rescue ActionView::MissingTemplate => e
update_details(:formats => nil) do
- raise unless exists?(layout)
+ raise unless template_exists?(layout)
end
end
end
+ def _find_layout(layout) #:nodoc:
+ layout =~ /^\// ?
+ with_fallbacks { find_template(layout) } : find_template(layout)
+ end
+
# Contains the logic that actually renders the layout.
def _render_layout(layout, locals, &block) #:nodoc:
layout.render(self, locals){ |*name| _layout_for(*name, &block) }
diff --git a/actionpack/lib/action_view/render/partials.rb b/actionpack/lib/action_view/render/partials.rb
index 950c9d2cd8..17d16556b9 100644
--- a/actionpack/lib/action_view/render/partials.rb
+++ b/actionpack/lib/action_view/render/partials.rb
@@ -123,7 +123,7 @@ module ActionView
# You can also apply a layout to a block within any template:
#
# <%# app/views/users/_chief.html.erb &>
- # <% render(:layout => "administrator", :locals => { :user => chief }) do %>
+ # <%= render(:layout => "administrator", :locals => { :user => chief }) do %>
# Title: <%= chief.title %>
# <% end %>
#
@@ -146,7 +146,7 @@ module ActionView
# </div>
#
# <%# app/views/users/index.html.erb &>
- # <% render :layout => @users do |user| %>
+ # <%= render :layout => @users do |user| %>
# Title: <%= user.title %>
# <% end %>
#
@@ -162,7 +162,7 @@ module ActionView
# </div>
#
# <%# app/views/users/index.html.erb &>
- # <% render :layout => @users do |user, section| %>
+ # <%= render :layout => @users do |user, section| %>
# <%- case section when :header -%>
# Title: <%= user.title %>
# <%- when :footer -%>
@@ -294,7 +294,7 @@ module ActionView
def find_template(path=@path)
return path unless path.is_a?(String)
prefix = @view.controller_path unless path.include?(?/)
- @view.find(path, prefix, true)
+ @view.find_template(path, prefix, true)
end
def partial_path(object = @object)
diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb
index 47ea70f5ad..492326964a 100644
--- a/actionpack/lib/action_view/render/rendering.rb
+++ b/actionpack/lib/action_view/render/rendering.rb
@@ -12,14 +12,17 @@ module ActionView
#
# 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.
- def render(options = {}, locals = {}, &block) #:nodoc:
+ def render(options = {}, locals = {}, &block)
case options
when Hash
if block_given?
- content = _render_partial(options.merge(:partial => options[:layout]), &block)
- safe_concat(content)
+ _render_partial(options.merge(:partial => options[:layout]), &block)
+ elsif options.key?(:partial)
+ _render_partial(options)
else
- _render(options)
+ template = _determine_template(options)
+ lookup_context.freeze_formats(template.formats, true)
+ _render_template(template, options[:layout], options)
end
when :update
update_page(&block)
@@ -28,44 +31,18 @@ module ActionView
end
end
- # This is the API to render a ViewContext's template from a controller.
- def render_template(options, &block)
- _evaluate_assigns_and_ivars
-
- # TODO Layout for partials should be handled here, because inside the
- # partial renderer it looks for the layout as a partial.
- if options.key?(:partial) && options[:layout]
- options[:layout] = _find_layout(options[:layout])
- end
-
- _render(options, &block)
- end
-
- # This method holds the common render logic for both controllers and
- # views rendering stacks.
- def _render(options) #:nodoc:
- if options.key?(:partial)
- _render_partial(options)
- else
- template = _determine_template(options)
- yield template if block_given?
- _render_template(template, options[:layout], options)
- end
- end
-
# Determine the template to be rendered using the given options.
def _determine_template(options) #:nodoc:
if options.key?(:inline)
handler = Template.handler_class_for_extension(options[:type] || "erb")
Template.new(options[:inline], "inline template", handler, {})
elsif options.key?(:text)
- Template::Text.new(options[:text], self.formats.try(:first))
- elsif options.key?(:_template)
- options[:_template]
+ Template::Text.new(options[:text], formats.try(:first))
elsif options.key?(:file)
- with_fallbacks { find(options[:file], options[:prefix]) }
+ with_fallbacks { find_template(options[:file], options[:prefix]) }
elsif options.key?(:template)
- find(options[:template], options[:prefix])
+ options[:template].respond_to?(:render) ?
+ options[:template] : find_template(options[:template], options[:prefix])
end
end
@@ -73,22 +50,17 @@ module ActionView
# supplied as well.
def _render_template(template, layout = nil, options = {}) #:nodoc:
locals = options[:locals] || {}
- layout = _find_layout(layout) if layout
+ layout = find_layout(layout) if layout
ActiveSupport::Notifications.instrument("action_view.render_template",
- :identifier => template.identifier, :layout => layout.try(:identifier)) do
+ :identifier => template.identifier, :layout => layout.try(:virtual_path)) do
content = template.render(self, locals) { |*name| _layout_for(*name) }
@_content_for[:layout] = content
- if layout
- @_layout = layout.identifier
- content = _render_layout(layout, locals)
- end
-
+ content = _render_layout(layout, locals) if layout
content
end
end
-
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index b4fdb49d3b..8abc1633ff 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -1,8 +1,7 @@
# encoding: utf-8
# This is so that templates compiled in this file are UTF-8
-
-require 'set'
-require "action_view/template/resolver"
+require 'active_support/core_ext/array/wrap'
+require 'active_support/core_ext/object/blank'
module ActionView
class Template
@@ -24,19 +23,20 @@ module ActionView
@identifier = identifier
@handler = handler
- @partial = details[:partial]
@virtual_path = details[:virtual_path]
@method_names = {}
- format = details[:format]
- format ||= handler.default_format.to_sym if handler.respond_to?(:default_format)
- format ||= :html
- @formats = [format.to_sym]
+ format = details[:format] || :html
+ @formats = Array.wrap(format).map(&:to_sym)
end
def render(view, locals, &block)
- method_name = compile(locals, view)
- view.send(method_name, locals, &block)
+ # Notice that we use a bang in this instrumentation because you don't want to
+ # consume this in production. This is only slow if it's being listened to.
+ ActiveSupport::Notifications.instrument("action_view.render_template!", :virtual_path => @virtual_path) do
+ method_name = compile(locals, view)
+ view.send(method_name, locals, &block)
+ end
rescue Exception => e
if e.is_a?(Template::Error)
e.sub_template_of(self)
@@ -58,10 +58,6 @@ module ActionView
@counter_name ||= "#{variable_name}_counter".to_sym
end
- def partial?
- @partial
- end
-
def inspect
if defined?(Rails.root)
identifier.sub("#{Rails.root}/", '')
diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb
index 648f708d3d..5222ffa89c 100644
--- a/actionpack/lib/action_view/template/error.rb
+++ b/actionpack/lib/action_view/template/error.rb
@@ -1,6 +1,26 @@
require "active_support/core_ext/enumerable"
module ActionView
+ class ActionViewError < StandardError #:nodoc:
+ end
+
+ class MissingTemplate < ActionViewError #:nodoc:
+ attr_reader :path
+
+ def initialize(paths, path, details, partial)
+ @path = path
+ display_paths = paths.compact.map{ |p| p.to_s.inspect }.join(", ")
+ template_type = if partial
+ "partial"
+ elsif path =~ /layouts/i
+ 'layout'
+ else
+ 'template'
+ end
+
+ super("Missing #{template_type} #{path} with #{details.inspect} in view paths #{display_paths}")
+ end
+ end
class Template
# The Template::Error exception is raised when the compilation of the template fails. This exception then gathers a
# bunch of intimate details and uses it to report a very precise exception message.
@@ -73,11 +93,11 @@ module ActionView
end
def to_s
- "\n#{self.class} (#{message}) #{source_location}:\n" +
+ "\n#{self.class} (#{message}) #{source_location}:\n" +
"#{source_extract}\n #{clean_backtrace.join("\n ")}\n\n"
end
- # don't do anything nontrivial here. Any raised exception from here becomes fatal
+ # don't do anything nontrivial here. Any raised exception from here becomes fatal
# (and can't be rescued).
def backtrace
@backtrace
diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb
index 937694ce8e..705c2bf82e 100644
--- a/actionpack/lib/action_view/template/handlers/erb.rb
+++ b/actionpack/lib/action_view/template/handlers/erb.rb
@@ -3,46 +3,16 @@ require 'active_support/core_ext/string/output_safety'
require 'erubis'
module ActionView
- class OutputBuffer
- def initialize
- @buffer = ActiveSupport::SafeBuffer.new
- end
-
- def safe_concat(value)
- @buffer.safe_concat(value)
- end
-
+ class OutputBuffer < ActiveSupport::SafeBuffer
def <<(value)
- @buffer << value.to_s
- end
-
- def length
- @buffer.length
+ super(value.to_s)
end
+ alias :append= :<<
- def [](*args)
- @buffer[*args]
- end
-
- def to_s
- @buffer.to_s
- end
-
- def to_str
- @buffer.to_str
- end
-
- def empty?
- @buffer.empty?
- end
-
- def html_safe?
- @buffer.html_safe?
- end
-
- if "".respond_to?(:force_encoding)
- def force_encoding(encoding)
- @buffer.force_encoding(encoding)
+ def append_if_string=(value)
+ if value.is_a?(String) && !value.is_a?(NonConcattingString)
+ ActiveSupport::Deprecation.warn("<% %> style block helpers are deprecated. Please use <%= %>", caller)
+ self << value
end
end
end
@@ -58,16 +28,26 @@ module ActionView
src << "@output_buffer.safe_concat('" << escape_text(text) << "');"
end
+ BLOCK_EXPR = /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
+
def add_expr_literal(src, code)
- if code =~ /(do|\{)(\s*\|[^|]*\|)?\s*\Z/
- src << '@output_buffer << ' << code
+ if code =~ BLOCK_EXPR
+ src << '@output_buffer.append= ' << code
+ else
+ src << '@output_buffer.append= (' << code << ');'
+ end
+ end
+
+ def add_stmt(src, code)
+ if code =~ BLOCK_EXPR
+ src << '@output_buffer.append_if_string= ' << code
else
- src << '@output_buffer << (' << code << ');'
+ super
end
end
def add_expr_escaped(src, code)
- src << '@output_buffer << ' << escaped_expr(code) << ';'
+ src << '@output_buffer.append= ' << escaped_expr(code) << ';'
end
def add_postamble(src)
diff --git a/actionpack/lib/action_view/template/handlers/rjs.rb b/actionpack/lib/action_view/template/handlers/rjs.rb
index 63e7dc0902..128be5077c 100644
--- a/actionpack/lib/action_view/template/handlers/rjs.rb
+++ b/actionpack/lib/action_view/template/handlers/rjs.rb
@@ -6,8 +6,7 @@ module ActionView
self.default_format = Mime::JS
def compile(template)
- "controller.response.content_type ||= Mime::JS;" +
- "update_page do |page|;#{template.source}\nend"
+ "update_page do |page|;#{template.source}\nend"
end
def default_format
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index a43597e728..8e8afaa43f 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -1,6 +1,5 @@
require "pathname"
require "active_support/core_ext/class"
-require "active_support/core_ext/array/wrap"
require "action_view/template"
module ActionView
@@ -14,15 +13,8 @@ module ActionView
@cached.clear
end
- def find(*args)
- find_all(*args).first
- end
-
# Normalizes the arguments and passes it on to find_template.
def find_all(name, prefix=nil, partial=false, details={}, key=nil)
- name, prefix = normalize_name(name, prefix)
- details = details.merge(:handlers => default_handlers)
-
cached(key, prefix, name, partial) do
find_templates(name, prefix, partial, details)
end
@@ -34,10 +26,6 @@ module ActionView
@caching ||= !defined?(Rails.application) || Rails.application.config.cache_classes
end
- def default_handlers
- Template::Handlers.extensions + [nil]
- end
-
# This is what child classes implement. No defaults are needed
# because Resolver guarantees that the arguments are present and
# normalized.
@@ -45,17 +33,6 @@ module ActionView
raise NotImplementedError
end
- # Support legacy foo.erb names even though we now ignore .erb
- # as well as incorrectly putting part of the path in the template
- # name instead of the prefix.
- def normalize_name(name, prefix)
- handlers = Template::Handlers.extensions.join('|')
- name = name.to_s.gsub(/\.(?:#{handlers})$/, '')
-
- parts = name.split('/')
- return parts.pop, [prefix, *parts].compact.join("/")
- end
-
def cached(key, prefix, name, partial)
return yield unless key && caching?
scope = @cached[key][prefix][name]
@@ -79,7 +56,7 @@ module ActionView
def find_templates(name, prefix, partial, details)
path = build_path(name, prefix, partial, details)
- query(partial, path, EXTENSION_ORDER.map { |ext| details[ext] })
+ query(path, EXTENSION_ORDER.map { |ext| details[ext] }, details[:formats])
end
def build_path(name, prefix, partial, details)
@@ -89,26 +66,32 @@ module ActionView
path
end
- def query(partial, path, exts)
+ def query(path, exts, formats)
query = File.join(@path, path)
exts.each do |ext|
- query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << '}'
+ query << '{' << ext.map {|e| e && ".#{e}" }.join(',') << ',}'
end
Dir[query].reject { |p| File.directory?(p) }.map do |p|
- handler, format = extract_handler_and_format(p)
+ handler, format = extract_handler_and_format(p, formats)
Template.new(File.read(p), File.expand_path(p), handler,
- :partial => partial, :virtual_path => path, :format => format)
+ :virtual_path => path, :format => format)
end
end
- def extract_handler_and_format(path)
+ # 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)
pieces = File.basename(path).split(".")
pieces.shift
- handler = Template.handler_class_for_extension(pieces.pop)
- format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym
+ handler = Template.handler_class_for_extension(pieces.pop)
+ format = pieces.last && Mime[pieces.last] && pieces.pop.to_sym
+ format ||= handler.default_format if handler.respond_to?(:default_format)
+ format ||= default_formats
+
[handler, format]
end
end
diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb
index df394b0fb0..269340514c 100644
--- a/actionpack/lib/action_view/template/text.rb
+++ b/actionpack/lib/action_view/template/text.rb
@@ -1,10 +1,12 @@
module ActionView #:nodoc:
class Template
class Text < String #:nodoc:
- def initialize(string, content_type = nil)
+ attr_accessor :mime_type
+
+ def initialize(string, mime_type = nil)
super(string.to_s)
- @content_type = Mime[content_type] || content_type if content_type
- @content_type ||= Mime::TEXT
+ @mime_type = Mime[mime_type] || mime_type if mime_type
+ @mime_type ||= Mime::TEXT
end
def identifier
@@ -19,12 +21,8 @@ module ActionView #:nodoc:
to_s
end
- def mime_type
- @content_type
- end
-
def formats
- [@content_type.to_sym]
+ [@mime_type.to_sym]
end
def partial?
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index 1578ac9479..b0ababe344 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -2,29 +2,10 @@ require 'action_controller/test_case'
require 'action_view'
module ActionView
- class Base
- alias_method :initialize_without_template_tracking, :initialize
- def initialize(*args)
- @_rendered = { :template => nil, :partials => Hash.new(0) }
- initialize_without_template_tracking(*args)
- end
-
- attr_internal :rendered
- end
-
- class Template
- alias_method :render_without_tracking, :render
- def render(view, locals, &blk)
- rendered = view.rendered
- rendered[:partials][self] += 1 if partial?
- rendered[:template] ||= []
- rendered[:template] << self
- render_without_tracking(view, locals, &blk)
- end
- end
-
class TestCase < ActiveSupport::TestCase
class TestController < ActionController::Base
+ include ActionDispatch::TestProcess
+
attr_accessor :request, :response, :params
def self.controller_path
@@ -42,6 +23,7 @@ module ActionView
end
include ActionDispatch::Assertions, ActionDispatch::TestProcess
+ include ActionController::TemplateAssertions
include ActionView::Context
include ActionController::PolymorphicRoutes
@@ -64,7 +46,7 @@ module ActionView
end
def config
- @controller.config
+ @controller.config if @controller.respond_to?(:config)
end
def render(options = {}, local_assigns = {}, &block)
@@ -124,7 +106,8 @@ module ActionView
def _view
view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller)
- view.class.send :include, _helpers
+ view.singleton_class.send :include, _helpers
+ view.singleton_class.send :include, @controller._router.url_helpers
view.output_buffer = self.output_buffer
view
end