aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/abstract_controller/base.rb16
-rw-r--r--actionpack/lib/abstract_controller/collector.rb6
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb15
-rw-r--r--actionpack/lib/abstract_controller/layouts.rb3
-rw-r--r--actionpack/lib/action_controller/caching.rb3
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb2
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb22
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb20
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb14
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb42
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb21
-rw-r--r--actionpack/lib/action_dispatch/middleware/public_exceptions.rb45
-rw-r--r--actionpack/lib/action_dispatch/request/session.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing.rb7
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb10
-rw-r--r--actionpack/lib/action_dispatch/routing/polymorphic_routes.rb8
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb10
-rw-r--r--actionpack/lib/action_dispatch/testing/integration.rb14
-rw-r--r--actionpack/lib/action_dispatch/testing/test_request.rb2
-rw-r--r--actionpack/lib/action_view.rb4
-rw-r--r--actionpack/lib/action_view/asset_paths.rb8
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb4
-rw-r--r--actionpack/lib/action_view/helpers/controller_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb94
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb52
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb214
-rw-r--r--actionpack/lib/action_view/helpers/tags.rb5
-rw-r--r--actionpack/lib/action_view/helpers/tags/color_field.rb25
-rw-r--r--actionpack/lib/action_view/helpers/tags/date_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_field.rb22
-rw-r--r--actionpack/lib/action_view/helpers/tags/datetime_local_field.rb19
-rw-r--r--actionpack/lib/action_view/helpers/tags/month_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/time_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/tags/week_field.rb13
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb34
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb51
-rw-r--r--actionpack/lib/action_view/locale/en.yml98
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb2
-rw-r--r--actionpack/lib/action_view/template/resolver.rb13
-rw-r--r--actionpack/lib/action_view/test_case.rb3
45 files changed, 573 insertions, 419 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb
index 97a9eec144..9c3960961b 100644
--- a/actionpack/lib/abstract_controller/base.rb
+++ b/actionpack/lib/abstract_controller/base.rb
@@ -51,7 +51,7 @@ module AbstractController
# to specify particular actions as hidden.
#
# ==== Returns
- # * <tt>array</tt> - An array of method names that should not be considered actions.
+ # * <tt>Array</tt> - An array of method names that should not be considered actions.
def hidden_actions
[]
end
@@ -63,7 +63,7 @@ module AbstractController
# itself. Finally, #hidden_actions are removed.
#
# ==== Returns
- # * <tt>set</tt> - A set of all methods that should be considered actions.
+ # * <tt>Set</tt> - A set of all methods that should be considered actions.
def action_methods
@action_methods ||= begin
# All public instance methods of this class, including ancestors
@@ -92,11 +92,12 @@ module AbstractController
# controller_path.
#
# ==== Returns
- # * <tt>string</tt>
+ # * <tt>String</tt>
def controller_path
@controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous?
end
+ # Refresh the cached action_methods when a new action_method is added.
def method_added(name)
super
clear_action_methods!
@@ -130,6 +131,7 @@ module AbstractController
self.class.controller_path
end
+ # Delegates to the class' #action_methods
def action_methods
self.class.action_methods
end
@@ -139,8 +141,14 @@ module AbstractController
#
# Notice that <tt>action_methods.include?("foo")</tt> may return
# false and <tt>available_action?("foo")</tt> returns true because
- # available action consider actions that are also available
+ # this method considers actions that are also available
# through other means, for example, implicit render ones.
+ #
+ # ==== Parameters
+ # * <tt>action_name</tt> - The name of an action to be tested
+ #
+ # ==== Returns
+ # * <tt>TrueClass</tt>, <tt>FalseClass</tt>
def available_action?(action_name)
method_for_action(action_name).present?
end
diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb
index 81fb514770..492329c401 100644
--- a/actionpack/lib/abstract_controller/collector.rb
+++ b/actionpack/lib/abstract_controller/collector.rb
@@ -4,7 +4,7 @@ module AbstractController
module Collector
def self.generate_method_for_mime(mime)
sym = mime.is_a?(Symbol) ? mime : mime.to_sym
- const = sym.to_s.upcase
+ const = sym.upcase
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{sym}(*args, &block) # def html(*args, &block)
custom(Mime::#{const}, *args, &block) # custom(Mime::HTML, *args, &block)
@@ -19,7 +19,7 @@ module AbstractController
protected
def method_missing(symbol, &block)
- mime_constant = Mime.const_get(symbol.to_s.upcase)
+ mime_constant = Mime.const_get(symbol.upcase)
if Mime::SET.include?(mime_constant)
AbstractController::Collector.generate_method_for_mime(mime_constant)
@@ -29,4 +29,4 @@ module AbstractController
end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 4e0672d590..d63d17f4c5 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -132,7 +132,11 @@ module AbstractController
case arg
when String, Symbol
file_name = "#{arg.to_s.underscore}_helper"
- require_dependency(file_name, "Missing helper file helpers/%s.rb")
+ begin
+ require_dependency(file_name)
+ rescue LoadError => e
+ raise MissingHelperError.new(e, file_name)
+ end
file_name.camelize.constantize
when Module
arg
@@ -142,6 +146,15 @@ module AbstractController
end
end
+ class MissingHelperError < LoadError
+ def initialize(error, path)
+ @error = error
+ @path = "helpers/#{path}.rb"
+ set_backtrace error.backtrace
+ super("Missing helper file helpers/%s.rb" % path)
+ end
+ end
+
private
# Makes all the (instance) methods in the helper module available to templates
# rendered through this controller.
diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb
index bc9f6fc3e8..c1b3994035 100644
--- a/actionpack/lib/abstract_controller/layouts.rb
+++ b/actionpack/lib/abstract_controller/layouts.rb
@@ -203,8 +203,7 @@ module AbstractController
include Rendering
included do
- class_attribute :_layout, :_layout_conditions,
- :instance_reader => false, :instance_writer => false
+ class_attribute :_layout, :_layout_conditions, :instance_accessor => false
self._layout = nil
self._layout_conditions = {}
_write_layout_method
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 112573a38d..9118806059 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -55,6 +55,9 @@ module ActionController #:nodoc:
end
end
+ include RackDelegation
+ include AbstractController::Callbacks
+
include ConfigMethods
include Pages, Actions, Fragments
include Sweeping if defined?(ActiveRecord)
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index dd4eddbe9a..73b8cd383c 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -110,7 +110,7 @@ module ActionController #:nodoc:
gzip_level = options.fetch(:gzip, page_cache_compression)
gzip_level = case gzip_level
when Symbol
- Zlib.const_get(gzip_level.to_s.upcase)
+ Zlib.const_get(gzip_level.upcase)
when Fixnum
gzip_level
when false
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index ac12cbb625..77d799a38a 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -40,15 +40,23 @@ module ActionController
def force_ssl(options = {})
host = options.delete(:host)
before_filter(options) do
- unless request.ssl?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
- flash.keep if respond_to?(:flash)
- redirect_to redirect_options
- end
+ force_ssl_redirect(host)
end
end
end
+
+ # Redirect the existing request to use the HTTPS protocol.
+ #
+ # ==== Parameters
+ # * <tt>host</tt> - Redirect to a different host name
+ def force_ssl_redirect(host = nil)
+ unless request.ssl?
+ redirect_options = {:protocol => 'https://', :status => :moved_permanently}
+ redirect_options.merge!(:host => host) if host
+ redirect_options.merge!(:params => request.query_parameters)
+ flash.keep if respond_to?(:flash)
+ redirect_to redirect_options
+ end
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index 86d061e3b7..66cdfd40ff 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -95,9 +95,9 @@ module ActionController
helpers = []
Array(path).each do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ helpers += names.sort
end
- helpers.sort!
helpers.uniq!
helpers
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 57bb0e2a32..a0d1064094 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -401,16 +401,20 @@ module ActionController
end
end
- # If token Authorization header is present, call the login procedure with
- # the present token and options.
+ # If token Authorization header is present, call the login
+ # procedure with the present token and options.
#
- # controller - ActionController::Base instance for the current request.
- # login_procedure - Proc to call if a token is present. The Proc should
- # take 2 arguments:
- # authenticate(controller) { |token, options| ... }
+ # [controller]
+ # ActionController::Base instance for the current request.
#
- # Returns the return value of `&login_procedure` if a token is found.
- # Returns nil if no token is found.
+ # [login_procedure]
+ # Proc to call if a token is present. The Proc should take two arguments:
+ #
+ # authenticate(controller) { |token, options| ... }
+ #
+ # Returns the return value of <tt>login_procedure</tt> if a
+ # token is found. Returns <tt>nil</tt> if no token is found.
+
def authenticate(controller, &login_procedure)
token, options = token_and_options(controller.request)
unless token.blank?
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 95b0e99ed5..53534c0307 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -14,6 +14,20 @@ module ActionController #:nodoc:
# authentication scheme there anyway). Also, GET requests are not protected as these
# should be idempotent.
#
+ # It's important to remember that XML or JSON requests are also affected and if
+ # you're building an API you'll need something like:
+ #
+ # class ApplicationController < ActionController::Base
+ # protect_from_forgery
+ # skip_before_filter :verify_authenticity_token, :if => :json_request?
+ #
+ # protected
+ #
+ # def json_request?
+ # request.format.json?
+ # end
+ # end
+ #
# CSRF protection is turned on with the <tt>protect_from_forgery</tt> method,
# which checks the token and resets the session if it doesn't match what was expected.
# A call to this method is generated for new \Rails applications by default.
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index 5ee4c044ea..0b895e7860 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -84,17 +84,29 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
+ SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
+
+ def cache_control_headers
+ cache_control = {}
+ if cc = self[CACHE_CONTROL]
+ cc.delete(' ').split(',').each do |segment|
+ directive, argument = segment.split('=', 2)
+ case directive
+ when *SPESHUL_KEYS
+ key = directive.tr('-', '_')
+ cache_control[key.to_sym] = argument || true
+ else
+ cache_control[:extras] ||= []
+ cache_control[:extras] << segment
+ end
+ end
+ end
+ cache_control
+ end
def prepare_cache_control!
- @cache_control = {}
+ @cache_control = cache_control_headers
@etag = self[ETAG]
-
- if cache_control = self[CACHE_CONTROL]
- cache_control.split(/,\s*/).each do |segment|
- first, last = segment.split("=")
- @cache_control[first.to_sym] = last || true
- end
- end
end
def handle_conditional_get!
@@ -110,14 +122,24 @@ module ActionDispatch
MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self[CACHE_CONTROL].present?
+ control = {}
+ cc_headers = cache_control_headers
+ if extras = cc_headers.delete(:extras)
+ @cache_control[:extras] ||= []
+ @cache_control[:extras] += extras
+ @cache_control[:extras].uniq!
+ end
- control = @cache_control
+ control.merge! cc_headers
+ control.merge! @cache_control
if control.empty?
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
headers[CACHE_CONTROL] = NO_CACHE
+ if control[:extras]
+ headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
+ end
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 0eaae80461..ee1913dbf9 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -99,9 +99,9 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms))
+ Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- SET << Mime.const_get(symbol.to_s.upcase)
+ SET << Mime.const_get(symbol.upcase)
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
@@ -194,7 +194,7 @@ module Mime
#
# Mime::Type.unregister(:mobile)
def unregister(symbol)
- symbol = symbol.to_s.upcase
+ symbol = symbol.upcase
mime = Mime.const_get(symbol)
Mime.instance_eval { remove_const(symbol) }
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index aa5ba3e8a5..8cea17c7a6 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -263,6 +263,27 @@ module ActionDispatch
LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
end
+ protected
+
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ hash.each_value do |v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+
+ def parse_query(qs)
+ deep_munge(super)
+ end
+
private
def check_method(name)
diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
index 85b8d178bf..53bedaa40a 100644
--- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
+++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb
@@ -1,5 +1,4 @@
module ActionDispatch
- # A simple Rack application that renders exceptions in the given public path.
class PublicExceptions
attr_accessor :public_path
@@ -8,23 +7,41 @@ module ActionDispatch
end
def call(env)
- status = env["PATH_INFO"][1..-1]
- locale_path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
- path = "#{public_path}/#{status}.html"
-
- if locale_path && File.exist?(locale_path)
- render(status, File.read(locale_path))
- elsif File.exist?(path)
- render(status, File.read(path))
+ exception = env["action_dispatch.exception"]
+ status = env["PATH_INFO"][1..-1]
+ request = ActionDispatch::Request.new(env)
+ content_type = request.formats.first
+ body = { :status => status, :error => exception.message }
+
+ render(status, content_type, body)
+ end
+
+ private
+
+ def render(status, content_type, body)
+ format = content_type && "to_#{content_type.to_sym}"
+ if format && body.respond_to?(format)
+ render_format(status, content_type, body.public_send(format))
else
- [404, { "X-Cascade" => "pass" }, []]
+ render_html(status)
end
end
- private
+ def render_format(status, content_type, body)
+ [status, {'Content-Type' => "#{content_type}; charset=#{ActionDispatch::Response.default_charset}",
+ 'Content-Length' => body.bytesize.to_s}, [body]]
+ end
+
+ def render_html(status)
+ found = false
+ path = "#{public_path}/#{status}.#{I18n.locale}.html" if I18n.locale
+ path = "#{public_path}/#{status}.html" unless path && (found = File.exist?(path))
- def render(status, body)
- [status, {'Content-Type' => "text/html; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]]
+ if found || File.exist?(path)
+ render_format(status, 'text/html', File.read(path))
+ else
+ [404, { "X-Cascade" => "pass" }, []]
+ end
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb
index 4ad7071820..d8bcc28613 100644
--- a/actionpack/lib/action_dispatch/request/session.rb
+++ b/actionpack/lib/action_dispatch/request/session.rb
@@ -87,6 +87,14 @@ module ActionDispatch
alias :key? :has_key?
alias :include? :has_key?
+ def keys
+ @delegate.keys
+ end
+
+ def values
+ @delegate.values
+ end
+
def []=(key, value)
load_for_write!
@delegate[key.to_s] = value
diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb
index 38a0270151..29090882a5 100644
--- a/actionpack/lib/action_dispatch/routing.rb
+++ b/actionpack/lib/action_dispatch/routing.rb
@@ -1,3 +1,4 @@
+# encoding: UTF-8
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/regexp'
@@ -218,6 +219,12 @@ module ActionDispatch
#
# match "/stories" => redirect("/posts")
#
+ # == Unicode character routes
+ #
+ # You can specify unicode character routes in your router:
+ #
+ # match "こんにちは" => "welcome#index"
+ #
# == Routing to Rack Applications
#
# Instead of a String, like <tt>posts#index</tt>, which corresponds to the
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index 67a208263b..94242ad962 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -1318,7 +1318,7 @@ module ActionDispatch
def draw(name)
path = @draw_paths.find do |_path|
- _path.join("#{name}.rb").file?
+ File.exists? "#{_path}/#{name}.rb"
end
unless path
@@ -1327,9 +1327,9 @@ module ActionDispatch
msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
raise ArgumentError, msg
end
-
- route_path = path.join("#{name}.rb")
- instance_eval(route_path.read, route_path.to_s)
+
+ route_path = "#{path}/#{name}.rb"
+ instance_eval(File.read(route_path), route_path.to_s)
end
# match 'path' => 'controller#action'
@@ -1387,7 +1387,7 @@ module ActionDispatch
options[:as] = name_for_action(options[:as], action)
end
- mapping = Mapping.new(@set, @scope, path, options)
+ mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
app, conditions, requirements, defaults, as, anchor = mapping.to_route
@set.add_route(app, conditions, requirements, defaults, as, anchor)
end
diff --git a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
index 8fde667108..86ce7a83b9 100644
--- a/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
+++ b/actionpack/lib/action_dispatch/routing/polymorphic_routes.rb
@@ -95,7 +95,7 @@ module ActionDispatch
end
record = extract_record(record_or_hash_or_array)
- record = record.to_model if record.respond_to?(:to_model)
+ record = convert_to_model(record)
args = Array === record_or_hash_or_array ?
record_or_hash_or_array.dup :
@@ -122,6 +122,8 @@ module ActionDispatch
args.last.kind_of?(Hash) ? args.last.merge!(url_options) : args << url_options
end
+ args.collect! { |a| convert_to_model(a) }
+
(proxy || self).send(named_route, *args)
end
@@ -152,6 +154,10 @@ module ActionDispatch
options[:action] ? "#{options[:action]}_" : ''
end
+ def convert_to_model(object)
+ object.respond_to?(:to_model) ? object.to_model : object
+ end
+
def routing_type(options)
options[:routing_type] || :url
end
diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb
index 7872f4007e..64b1d58ae9 100644
--- a/actionpack/lib/action_dispatch/routing/route_set.rb
+++ b/actionpack/lib/action_dispatch/routing/route_set.rb
@@ -252,15 +252,11 @@ module ActionDispatch
self.draw_paths = []
self.request_class = request_class
- @valid_conditions = {}
-
+ @valid_conditions = { :controller => true, :action => true }
request_class.public_instance_methods.each { |m|
- @valid_conditions[m.to_sym] = true
+ @valid_conditions[m] = true
}
- @valid_conditions[:controller] = true
- @valid_conditions[:action] = true
-
- self.valid_conditions.delete(:id)
+ @valid_conditions.delete(:id)
@append = []
@prepend = []
diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb
index 08fd28d72d..50ca28395b 100644
--- a/actionpack/lib/action_dispatch/testing/integration.rb
+++ b/actionpack/lib/action_dispatch/testing/integration.rb
@@ -17,8 +17,8 @@ module ActionDispatch
# a Hash, or a String that is appropriately encoded
# (<tt>application/x-www-form-urlencoded</tt> or
# <tt>multipart/form-data</tt>).
- # - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
- # automatically be upcased, with the prefix 'HTTP_' added if needed.
+ # - +headers+: Additional headers to pass, as a Hash. The headers will be
+ # merged into the Rack env hash.
#
# This method returns an Response object, which one can use to
# inspect the details of the response. Furthermore, if this method was
@@ -73,8 +73,7 @@ module ActionDispatch
#
# The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or
# +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart
- # string; the headers are a hash. Keys are automatically upcased and
- # prefixed with 'HTTP_' if not already.
+ # string; the headers are a hash.
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@@ -194,8 +193,11 @@ module ActionDispatch
# If the app is a Rails app, make url_helpers available on the session
# This makes app.url_for and app.foo_path available in the console
- if app.respond_to?(:routes) && app.routes.respond_to?(:url_helpers)
- singleton_class.class_eval { include app.routes.url_helpers }
+ if app.respond_to?(:routes)
+ singleton_class.class_eval do
+ include app.routes.url_helpers if app.routes.respond_to?(:url_helpers)
+ include app.routes.mounted_helpers if app.routes.respond_to?(:mounted_helpers)
+ end
end
reset!
diff --git a/actionpack/lib/action_dispatch/testing/test_request.rb b/actionpack/lib/action_dispatch/testing/test_request.rb
index d04be2099c..a86b510719 100644
--- a/actionpack/lib/action_dispatch/testing/test_request.rb
+++ b/actionpack/lib/action_dispatch/testing/test_request.rb
@@ -11,7 +11,7 @@ module ActionDispatch
end
def initialize(env = {})
- env = Rails.application.env_config.merge(env) if defined?(Rails.application)
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
super(DEFAULT_ENV.merge(env))
self.host = 'test.host'
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 3823f87027..03dfa110e3 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -36,7 +36,7 @@ module ActionView
autoload :LookupContext
autoload :PathSet
autoload :Template
- autoload :TestCase
+
autoload_under "renderer" do
autoload :Renderer
@@ -73,6 +73,8 @@ module ActionView
end
end
+ autoload :TestCase
+
ENCODING_FLAG = '#.*coding[:=]\s*(\S+)[ \t]*'
end
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb
index 4ce41d51f1..81880d17ea 100644
--- a/actionpack/lib/action_view/asset_paths.rb
+++ b/actionpack/lib/action_view/asset_paths.rb
@@ -35,7 +35,13 @@ module ActionView
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
- File.join(config.assets_dir, dir, source)
+
+ sources = []
+ sources << config.assets_dir
+ sources << dir unless source[0] == ?/
+ sources << source
+
+ File.join(sources)
end
def is_uri?(path)
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
index 05d5f1870a..1a54ca2399 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb
@@ -7,7 +7,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class AssetIncludeTag
+ class AssetIncludeTag #:nodoc:
include TagHelper
attr_reader :config, :asset_paths
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
index 4292d29f60..e0dbfe62c6 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class JavascriptIncludeTag < AssetIncludeTag
+ class JavascriptIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'javascript'
end
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
index 57b0627225..91318b2812 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb
@@ -6,7 +6,7 @@ module ActionView
module Helpers
module AssetTagHelper
- class StylesheetIncludeTag < AssetIncludeTag
+ class StylesheetIncludeTag < AssetIncludeTag #:nodoc:
def asset_name
'stylesheet'
end
@@ -53,7 +53,7 @@ module ActionView
# If the +source+ filename has no extension, <tt>.css</tt> will be appended (except for explicit URIs).
# Full paths from the document root will be passed through.
# Used internally by +stylesheet_link_tag+ to build the stylesheet path.
- #
+ #
# stylesheet_path "style" # => /stylesheets/style.css
# stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css
# stylesheet_path "/dir/style.css" # => /dir/style.css
diff --git a/actionpack/lib/action_view/helpers/controller_helper.rb b/actionpack/lib/action_view/helpers/controller_helper.rb
index 1a583e62ae..74ef25f7c1 100644
--- a/actionpack/lib/action_view/helpers/controller_helper.rb
+++ b/actionpack/lib/action_view/helpers/controller_helper.rb
@@ -10,14 +10,16 @@ module ActionView
delegate :request_forgery_protection_token, :params, :session, :cookies, :response, :headers,
:flash, :action_name, :controller_name, :controller_path, :to => :controller
- delegate :logger, :to => :controller, :allow_nil => true
-
def assign_controller(controller)
if @_controller = controller
@_request = controller.request if controller.respond_to?(:request)
@_config = controller.config.inheritable_copy if controller.respond_to?(:config)
end
end
+
+ def logger
+ controller.logger if controller.respond_to?(:logger)
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 6510610034..7dd35f7357 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -342,7 +342,7 @@ module ActionView
# Example:
#
# <%= form_for(@post) do |f| %>
- # <% f.fields_for(:comments, :include_id => false) do |cf| %>
+ # <%= f.fields_for(:comments, :include_id => false) do |cf| %>
# ...
# <% end %>
# <% end %>
@@ -939,6 +939,15 @@ module ActionView
Tags::RadioButton.new(object_name, method, self, tag_value, options).render
end
+ # Returns a text_field of type "color".
+ #
+ # color_field("car", "color")
+ # # => <input id="car_color" name="car[color]" type="color" value="#000000" />
+ #
+ def color_field(object_name, method, options = {})
+ Tags::ColorField.new(object_name, method, self, options).render
+ end
+
# Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by
# some browsers.
@@ -1007,6 +1016,74 @@ module ActionView
Tags::TimeField.new(object_name, method, self, options).render
end
+ # Returns a text_field of type "datetime".
+ #
+ # datetime_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 12)
+ # datetime_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" />
+ #
+ def datetime_field(object_name, method, options = {})
+ Tags::DatetimeField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "datetime-local".
+ #
+ # datetime_local_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 12)
+ # datetime_local_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" />
+ #
+ def datetime_local_field(object_name, method, options = {})
+ Tags::DatetimeLocalField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "month".
+ #
+ # month_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="month" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-%m"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 1, 27)
+ # month_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" />
+ #
+ def month_field(object_name, method, options = {})
+ Tags::MonthField.new(object_name, method, self, options).render
+ end
+
+ # Returns a text_field of type "week".
+ #
+ # week_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="week" />
+ #
+ # The default value is generated by trying to call +strftime+ with "%Y-W%W"
+ # on the object's value, which makes it behave as expected for instances
+ # of DateTime and ActiveSupport::TimeWithZone.
+ #
+ # @user.born_on = Date.new(1984, 5, 12)
+ # week_field("user", "born_on")
+ # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" />
+ #
+ def week_field(object_name, method, options = {})
+ Tags::WeekField.new(object_name, method, self, options).render
+ end
+
# Returns a text_field of type "url".
#
# url_field("user", "homepage")
@@ -1234,10 +1311,21 @@ module ActionView
# post:
# create: "Add %{model}"
#
- def button(value=nil, options={})
+ # ==== Examples
+ # button("Create a post")
+ # # => <button name='button' type='submit'>Create post</button>
+ #
+ # button do
+ # content_tag(:strong, 'Ask me!')
+ # end
+ # # => <button name='button' type='submit'>
+ # # <strong>Ask me!</strong>
+ # # </button>
+ #
+ def button(value = nil, options = {}, &block)
value, options = nil, value if value.is_a?(Hash)
value ||= submit_default_value
- @template.button_tag(value, options)
+ @template.button_tag(value, options, &block)
end
def emitted_hidden_id?
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e65b4e3e95..1a0019a48c 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -524,6 +524,14 @@ module ActionView
output.safe_concat("</fieldset>")
end
+ # Creates a text field of type "color".
+ #
+ # ==== Options
+ # * Accepts the same options as text_field_tag.
+ def color_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "color"))
+ end
+
# Creates a text field of type "search".
#
# ==== Options
@@ -560,6 +568,50 @@ module ActionView
text_field_tag(name, value, options.stringify_keys.update("type" => "time"))
end
+ # Creates a text field of type "datetime".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def datetime_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "datetime"))
+ end
+
+ # Creates a text field of type "datetime-local".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def datetime_local_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local"))
+ end
+
+ # Creates a text field of type "month".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def month_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "month"))
+ end
+
+ # Creates a text field of type "week".
+ #
+ # === Options
+ # * <tt>:min</tt> - The minimum acceptable value.
+ # * <tt>:max</tt> - The maximum acceptable value.
+ # * <tt>:step</tt> - The acceptable value granularity.
+ # * Otherwise accepts the same options as text_field_tag.
+ def week_field_tag(name, value = nil, options = {})
+ text_field_tag(name, value, options.stringify_keys.update("type" => "week"))
+ end
+
# Creates a text field of type "url".
#
# ==== Options
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index dfc26acfad..8f97d1f014 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -1,8 +1,8 @@
# encoding: utf-8
-require 'active_support/core_ext/big_decimal/conversions'
-require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/string/output_safety'
+require 'active_support/number_helper'
module ActionView
# = Action View Number Helpers
@@ -16,9 +16,6 @@ module ActionView
# unchanged if can't be converted into a valid number.
module NumberHelper
- DEFAULT_CURRENCY_VALUES = { :format => "%u%n", :negative_format => "-%u%n", :unit => "$", :separator => ".", :delimiter => ",",
- :precision => 2, :significant => false, :strip_insignificant_zeros => false }
-
# Raised when argument +number+ param given to the helpers is invalid and
# the option :raise is set to +true+.
class InvalidNumberError < StandardError
@@ -63,25 +60,7 @@ module ActionView
options = options.symbolize_keys
parse_float(number, true) if options[:raise]
-
- number = number.to_s.strip
- area_code = options[:area_code]
- delimiter = options[:delimiter] || "-"
- extension = options[:extension]
- country_code = options[:country_code]
-
- if area_code
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
- else
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
- number.slice!(0, 1) if number.start_with?(delimiter) && !delimiter.blank?
- end
-
- str = ''
- str << "+#{country_code}#{delimiter}" unless country_code.blank?
- str << number
- str << " x #{extension}" unless extension.blank?
- ERB::Util.html_escape(str)
+ ERB::Util.html_escape(ActiveSupport::NumberHelper.number_to_phone(number, options))
end
# Formats a +number+ into a currency string (e.g., $13.65). You
@@ -128,34 +107,9 @@ module ActionView
# # => 1234567890,50 &pound;
def number_to_currency(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- currency = translations_for('currency', options[:locale])
- currency[:negative_format] ||= "-" + currency[:format] if currency[:format]
-
- defaults = DEFAULT_CURRENCY_VALUES.merge(defaults_translations(options[:locale])).merge!(currency)
- defaults[:negative_format] = "-" + options[:format] if options[:format]
- options = defaults.merge!(options)
-
- unit = options.delete(:unit)
- format = options.delete(:format)
-
- if number.to_f < 0
- format = options.delete(:negative_format)
- number = number.respond_to?("abs") ? number.abs : number.sub(/^-/, '')
- end
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub('%n', value).gsub('%u', unit).html_safe
- 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
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_currency(number, options) }
end
# Formats a +number+ as a percentage string (e.g., 65%). You can
@@ -196,24 +150,9 @@ module ActionView
# number_to_percentage("98a", :raise => true) # => InvalidNumberError
def number_to_percentage(number, options = {})
return unless number
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- defaults = format_translations('percentage', options[:locale])
- options = defaults.merge!(options)
-
- format = options[:format] || "%n%"
-
- begin
- value = number_with_precision(number, options.merge(:raise => true))
- format.gsub(/%n/, value).html_safe
- rescue InvalidNumberError => e
- if options[:raise]
- raise
- else
- formatted_number = format.gsub(/%n/, e.number)
- e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number
- end
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_percentage(number, options) }
end
# Formats a +number+ with grouped thousands using +delimiter+
@@ -246,15 +185,9 @@ module ActionView
#
# number_with_delimiter("112a", :raise => true) # => raise InvalidNumberError
def number_with_delimiter(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- parse_float(number, options[:raise]) or return number
-
- options = defaults_translations(options[:locale]).merge(options)
-
- parts = number.to_s.to_str.split('.')
- parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}")
- safe_join(parts, options[:separator])
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_delimited(number, options) }
end
# Formats a +number+ with the specified level of
@@ -299,41 +232,11 @@ module ActionView
# number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.')
# # => 1.111,23
def number_with_precision(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('precision', options[:locale])
- options = defaults.merge!(options)
-
- precision = options.delete :precision
- significant = options.delete :significant
- strip_insignificant_zeros = options.delete :strip_insignificant_zeros
-
- if significant and precision > 0
- if number == 0
- digits, rounded_number = 1, 0
- else
- digits = (Math.log10(number.abs) + 1).floor
- rounded_number = (BigDecimal.new(number.to_s) / BigDecimal.new((10 ** (digits - precision)).to_f.to_s)).round.to_f * 10 ** (digits - precision)
- digits = (Math.log10(rounded_number.abs) + 1).floor # After rounding, the number of digits may have changed
- end
- precision -= digits
- precision = precision > 0 ? precision : 0 #don't let it be negative
- else
- rounded_number = BigDecimal.new(number.to_s).round(precision).to_f
- rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros
- end
- 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
- formatted_number
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_rounded(number, options) }
end
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze
# Formats the bytes in +number+ into a more understandable
# representation (e.g., giving it 1500 yields 1.5 KB). This
@@ -383,40 +286,11 @@ module ActionView
# number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB"
# number_to_human_size(524288000, :precision => 5) # => "500 MB"
def number_to_human_size(number, options = {})
- options = options.symbolize_keys
-
- number = (parse_float(number, options[:raise]) or return number)
-
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
-
- #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)
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- base = options[:prefix] == :si ? 1000 : 1024
-
- if number.to_i < base
- 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).html_safe
- else
- max_exp = STORAGE_UNITS.size - 1
- exponent = (Math.log(number) / Math.log(base)).to_i # Convert to base
- exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit
- number /= base ** exponent
-
- unit_key = STORAGE_UNITS[exponent]
- unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true)
-
- formatted_number = number_with_precision(number, options)
- storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe
- end
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human_size(number, options) }
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
@@ -516,60 +390,34 @@ module ActionView
# number_to_human(0.34, :units => :distance) # => "34 centimeters"
#
def number_to_human(number, options = {})
- options = options.symbolize_keys
+ options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
- number = (parse_float(number, options[:raise]) or return number)
+ wrap_with_output_safety_handling(number, options[:raise]){ ActiveSupport::NumberHelper.number_to_human(number, options) }
+ end
- defaults = format_translations('human', options[:locale])
- options = defaults.merge!(options)
+ private
- #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)
+ def escape_unsafe_delimiters_and_separators(options)
+ options[:separator] = ERB::Util.html_escape(options[:separator]) if options[:separator] && !options[:separator].html_safe?
+ options[:delimiter] = ERB::Util.html_escape(options[:delimiter]) if options[:delimiter] && !options[:delimiter].html_safe?
+ options
+ end
- inverted_du = DECIMAL_UNITS.invert
+ def wrap_with_output_safety_handling(number, raise_on_invalid, &block)
+ valid_float = valid_float?(number)
+ raise InvalidNumberError, number if raise_on_invalid && !valid_float
- 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| inverted_du[e_name] }.sort_by{|e| -e}
+ formatted_number = yield
- number_exponent = number != 0 ? Math.log10(number.abs).floor : 0
- display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0
- 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)
+ if valid_float || number.html_safe?
+ formatted_number.html_safe
else
- I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i)
+ formatted_number
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
-
- private
-
- def format_translations(namespace, locale)
- defaults_translations(locale).merge(translations_for(namespace, locale))
- end
-
- def defaults_translations(locale)
- I18n.translate(:'number.format', :locale => locale, :default => {})
end
- def translations_for(namespace, locale)
- I18n.translate(:"number.#{namespace}.format", :locale => locale, :default => {})
+ def valid_float?(number)
+ !parse_float(number, false).nil?
end
def parse_float(number, raise_error)
diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb
index 5cd77c8ec3..a05e16979a 100644
--- a/actionpack/lib/action_view/helpers/tags.rb
+++ b/actionpack/lib/action_view/helpers/tags.rb
@@ -8,14 +8,18 @@ module ActionView
autoload :CollectionCheckBoxes
autoload :CollectionRadioButtons
autoload :CollectionSelect
+ autoload :ColorField
autoload :DateField
autoload :DateSelect
+ autoload :DatetimeField
+ autoload :DatetimeLocalField
autoload :DatetimeSelect
autoload :EmailField
autoload :FileField
autoload :GroupedCollectionSelect
autoload :HiddenField
autoload :Label
+ autoload :MonthField
autoload :NumberField
autoload :PasswordField
autoload :RadioButton
@@ -29,6 +33,7 @@ module ActionView
autoload :TimeSelect
autoload :TimeZoneSelect
autoload :UrlField
+ autoload :WeekField
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb
new file mode 100644
index 0000000000..6f08f8483a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/color_field.rb
@@ -0,0 +1,25 @@
+module ActionView
+ module Helpers
+ module Tags
+ class ColorField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { validate_color_string(value(object)) }
+ @options = options
+ super
+ end
+
+ private
+
+ def validate_color_string(string)
+ regex = /#[0-9a-fA-F]{6}/
+ if regex.match(string)
+ string.downcase
+ else
+ "#000000"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb
index 0e79609d52..64c29dea3d 100644
--- a/actionpack/lib/action_view/helpers/tags/date_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/date_field.rb
@@ -1,13 +1,12 @@
module ActionView
module Helpers
module Tags
- class DateField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { value(object).try(:to_date) }
- @options = options
- super
- end
+ class DateField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%d")
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
new file mode 100644
index 0000000000..e407146e96
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_field.rb
@@ -0,0 +1,22 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeField < TextField #:nodoc:
+ def render
+ options = @options.stringify_keys
+ options["value"] = @options.fetch("value") { format_date(value(object)) }
+ options["min"] = format_date(options["min"])
+ options["max"] = format_date(options["max"])
+ @options = options
+ super
+ end
+
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%dT%T.%L%z")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
new file mode 100644
index 0000000000..6668d6d718
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb
@@ -0,0 +1,19 @@
+module ActionView
+ module Helpers
+ module Tags
+ class DatetimeLocalField < DatetimeField #:nodoc:
+ class << self
+ def field_type
+ @field_type ||= "datetime-local"
+ end
+ end
+
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m-%dT%T")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb
new file mode 100644
index 0000000000..3d3c32d847
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/month_field.rb
@@ -0,0 +1,13 @@
+module ActionView
+ module Helpers
+ module Tags
+ class MonthField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-%m")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb
index 271dc00c54..a3941860c9 100644
--- a/actionpack/lib/action_view/helpers/tags/time_field.rb
+++ b/actionpack/lib/action_view/helpers/tags/time_field.rb
@@ -1,13 +1,12 @@
module ActionView
module Helpers
module Tags
- class TimeField < TextField #:nodoc:
- def render
- options = @options.stringify_keys
- options["value"] = @options.fetch("value") { value(object).try(:strftime, "%T.%L") }
- @options = options
- super
- end
+ class TimeField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%T.%L")
+ end
end
end
end
diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb
new file mode 100644
index 0000000000..1e13939a0a
--- /dev/null
+++ b/actionpack/lib/action_view/helpers/tags/week_field.rb
@@ -0,0 +1,13 @@
+module ActionView
+ module Helpers
+ module Tags
+ class WeekField < DatetimeField #:nodoc:
+ private
+
+ def format_date(value)
+ value.try(:strftime, "%Y-W%W")
+ end
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb
index 8cd7cf0052..0cc0d069ea 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -62,9 +62,11 @@ module ActionView
#
# Pass a <tt>:separator</tt> to truncate +text+ at a natural break.
#
- # The result is not marked as HTML-safe, so will be subject to the default escaping when
- # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags
- # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags).
+ # Pass a block if you want to show extra content when the text is truncated.
+ #
+ # The result is marked as HTML-safe, but it is escaped by default, unless <tt>:escape</tt> is
+ # +false+. Care should be taken if +text+ contains HTML tags or entities, because truncation
+ # may produce invalid HTML (such as unbalanced or incomplete tags).
#
# truncate("Once upon a time in a world far far away")
# # => "Once upon a time in a world..."
@@ -80,8 +82,18 @@ module ActionView
#
# truncate("<p>Once upon a time in a world far far away</p>")
# # => "<p>Once upon a time in a wo..."
- def truncate(text, options = {})
- text.truncate(options.fetch(:length, 30), options) if text
+ #
+ # truncate("Once upon a time in a world far far away") { link_to "Continue", "#" }
+ # # => "Once upon a time in a wo...<a href="#">Continue</a>"
+ def truncate(text, options = {}, &block)
+ if text
+ length = options.fetch(:length, 30)
+
+ content = text.truncate(length, options)
+ content = options[:escape] == false ? content.html_safe : ERB::Util.html_escape(content)
+ content << capture(&block) if block_given? && text.length > length
+ content
+ end
end
# Highlights one or more +phrases+ everywhere in +text+ by inserting it into
@@ -102,7 +114,7 @@ module ActionView
# # => You searched for: <a href="search?q=rails">rails</a>
def highlight(text, phrases, options = {})
highlighter = options.fetch(:highlighter, '<mark>\1</mark>')
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
if text.blank? || phrases.blank?
text
@@ -165,12 +177,12 @@ module ActionView
# pluralize(0, 'person')
# # => 0 people
def pluralize(count, singular, plural = nil)
- word = if (count == 1 || count =~ /^1(\.0+)?$/)
- singular
+ word = if (count == 1 || count =~ /^1(\.0+)?$/)
+ singular
else
plural || singular.pluralize
end
-
+
"#{count || 0} #{word}"
end
@@ -215,7 +227,7 @@ module ActionView
#
# simple_format(my_text)
# # => "<p>Here is some basic text...\n<br />...with a line break.</p>"
- #
+ #
# simple_format(my_text, {}, :wrapper_tag => "div")
# # => "<div>Here is some basic text...\n<br />...with a line break.</div>"
#
@@ -231,7 +243,7 @@ module ActionView
# # => "<p><span>I'm allowed!</span> It's true.</p>"
def simple_format(text, html_options = {}, options = {})
wrapper_tag = options.fetch(:wrapper_tag, :p)
-
+
text = sanitize(text) if options.fetch(:sanitize, true)
paragraphs = split_paragraphs(text)
diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 8171bea8ed..552c9ba660 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -64,7 +64,7 @@ module ActionView
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
#
- # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
+ # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
# for more information.
def localize(*args)
I18n.localize(*args)
@@ -96,7 +96,7 @@ module ActionView
new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) }
break
else
- new_defautls << key
+ new_defaults << key
end
end
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index 7e69547dab..7f5b3c8a0f 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -233,25 +233,15 @@ module ActionView
#
# link_to("Destroy", "http://www.example.com", :method => :delete, :confirm => "Are you sure?")
# # => <a href='http://www.example.com' rel="nofollow" data-method="delete" data-confirm="Are you sure?">Destroy</a>
- def link_to(*args, &block)
- if block_given?
- options = args.first || {}
- html_options = args.second
- link_to(capture(&block), options, html_options)
- else
- name = args[0]
- options = args[1] || {}
- html_options = args[2]
-
- html_options = convert_options_to_data_attributes(options, html_options)
- url = url_for(options)
+ def link_to(name = nil, options = nil, html_options = nil, &block)
+ html_options, options = options, name if block_given?
+ options ||= {}
+ url = url_for(options)
- href = html_options['href']
- tag_options = tag_options(html_options)
+ html_options = convert_options_to_data_attributes(options, html_options)
+ html_options['href'] ||= url
- href_attr = "href=\"#{ERB::Util.html_escape(url)}\"" unless href
- "<a #{href_attr}#{tag_options}>#{ERB::Util.html_escape(name || url)}</a>".html_safe
- end
+ content_tag(:a, name || url, html_options, &block)
end
# Generates a form containing a single button that submits to the URL created
@@ -294,6 +284,16 @@ module ActionView
# # <div><input value="New" type="submit" /></div>
# # </form>"
#
+ # <%= button_to [:make_happy, @user] do %>
+ # Make happy <strong><%= @user.name %></strong>
+ # <% end %>
+ # # => "<form method="post" action="/users/1/make_happy" class="button_to">
+ # # <div>
+ # # <button type="submit">
+ # # Make happy <strong><%= @user.name %></strong>
+ # # </button>
+ # # </div>
+ # # </form>"
#
# <%= button_to "New", :action => "new", :form_class => "new-thing" %>
# # => "<form method="post" action="/controller/new" class="new-thing">
@@ -331,7 +331,11 @@ module ActionView
# # </div>
# # </form>"
# #
- def button_to(name, options = {}, html_options = {})
+ def button_to(name = nil, options = nil, html_options = nil, &block)
+ html_options, options = options, name if block_given?
+ options ||= {}
+ html_options ||= {}
+
html_options = html_options.stringify_keys
convert_boolean_attributes!(html_options, %w(disabled))
@@ -350,9 +354,16 @@ module ActionView
request_token_tag = form_method == 'post' ? token_tag : ''
html_options = convert_options_to_data_attributes(options, html_options)
- html_options.merge!("type" => "submit", "value" => name || url)
+ html_options['type'] = 'submit'
+
+ button = if block_given?
+ content_tag('button', html_options, &block)
+ else
+ html_options['value'] = name || url
+ tag('input', html_options)
+ end
- inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag
+ inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
content_tag('form', content_tag('div', inner_tags), form_options)
end
diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml
index 8e9db634fb..8a56f147b8 100644
--- a/actionpack/lib/action_view/locale/en.yml
+++ b/actionpack/lib/action_view/locale/en.yml
@@ -1,102 +1,4 @@
"en":
- number:
- # Used in number_with_delimiter()
- # These are also the defaults for 'currency', 'percentage', 'precision', and 'human'
- format:
- # Sets the separator between the units, for more precision (e.g. 1.0 / 2.0 == 0.5)
- separator: "."
- # Delimits thousands (e.g. 1,000,000 is a million) (always in groups of three)
- 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:
- format:
- # Where is the currency sign? %u is the currency unit, %n the number (default: $5.00)
- format: "%u%n"
- unit: "$"
- # 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 five are to override number.format and are optional
- # separator:
- delimiter: ""
- # precision:
- # significant: false
- # strip_insignificant_zeros: false
- format: "%n%"
-
- # Used in number_to_precision()
- precision:
- format:
- # 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() and number_to_human()
- human:
- format:
- # These five are to override number.format and are optional
- # separator:
- delimiter: ""
- 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)
- format: "%n %u"
- units:
- byte:
- one: "Byte"
- other: "Bytes"
- kb: "KB"
- 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:
distance_in_words:
diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb
index ae923de24e..82892593f8 100644
--- a/actionpack/lib/action_view/renderer/template_renderer.rb
+++ b/actionpack/lib/action_view/renderer/template_renderer.rb
@@ -35,7 +35,7 @@ module ActionView
end
end
- # Renders the given template. An string representing the layout can be
+ # Renders the given template. A string representing the layout can be
# supplied as well.
def render_template(template, layout_name = nil, locals = {}) #:nodoc:
view, locals = @view, locals || {}
diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb
index ab09dbece4..047e3fb1f9 100644
--- a/actionpack/lib/action_view/template/resolver.rb
+++ b/actionpack/lib/action_view/template/resolver.rb
@@ -8,7 +8,7 @@ module ActionView
# = Action View Resolver
class Resolver
# Keeps all information about view path and builds virtual path.
- class Path < String
+ class Path
attr_reader :name, :prefix, :partial, :virtual
alias_method :partial?, :partial
@@ -20,9 +20,16 @@ module ActionView
end
def initialize(name, prefix, partial, virtual)
- @name, @prefix, @partial = name, prefix, partial
- super(virtual)
+ @name = name
+ @prefix = prefix
+ @partial = partial
+ @virtual = virtual
end
+
+ def to_str
+ @virtual
+ end
+ alias :to_s :to_str
end
# Threadsafe template cache
diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb
index fece499c94..53bde48e4d 100644
--- a/actionpack/lib/action_view/test_case.rb
+++ b/actionpack/lib/action_view/test_case.rb
@@ -229,7 +229,8 @@ module ActionView
def method_missing(selector, *args)
if @controller.respond_to?(:_routes) &&
- @controller._routes.named_routes.helpers.include?(selector)
+ ( @controller._routes.named_routes.helpers.include?(selector) ||
+ @controller._routes.mounted_helpers.method_defined?(selector) )
@controller.__send__(selector, *args)
else
super