aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rw-r--r--actionpack/lib/action_controller/assertions/response_assertions.rb4
-rw-r--r--actionpack/lib/action_controller/base.rb20
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb4
-rw-r--r--actionpack/lib/action_controller/cgi_process.rb45
-rw-r--r--actionpack/lib/action_controller/filters.rb19
-rw-r--r--actionpack/lib/action_controller/headers.rb30
-rw-r--r--actionpack/lib/action_controller/rack_process.rb52
-rw-r--r--actionpack/lib/action_controller/request.rb165
-rw-r--r--actionpack/lib/action_controller/resources.rb42
-rw-r--r--actionpack/lib/action_controller/response.rb54
-rw-r--r--actionpack/lib/action_controller/routing/segments.rb3
-rw-r--r--actionpack/lib/action_controller/session/cookie_store.rb2
-rw-r--r--actionpack/lib/action_controller/test_process.rb44
-rw-r--r--actionpack/lib/action_view/base.rb5
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb576
-rw-r--r--actionpack/lib/action_view/helpers/form_tag_helper.rb4
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb56
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb4
-rw-r--r--actionpack/lib/action_view/partials.rb2
-rw-r--r--actionpack/lib/action_view/renderable.rb6
-rw-r--r--actionpack/lib/action_view/template.rb10
23 files changed, 689 insertions, 467 deletions
diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb
index 765225ae24..e2e8bbdc71 100644
--- a/actionpack/lib/action_controller/assertions/response_assertions.rb
+++ b/actionpack/lib/action_controller/assertions/response_assertions.rb
@@ -87,11 +87,11 @@ module ActionController
#
def assert_template(expected = nil, message=nil)
clean_backtrace do
- rendered = @response.rendered_template
+ rendered = @response.rendered_template.to_s
msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered)
assert_block(msg) do
if expected.nil?
- @response.rendered_template.nil?
+ @response.rendered_template.blank?
else
rendered.to_s.match(expected)
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 5689a9825e..0fdbcbd26f 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -428,11 +428,7 @@ module ActionController #:nodoc:
# By default, all methods defined in ActionController::Base and included modules are hidden.
# More methods can be hidden using <tt>hide_actions</tt>.
def hidden_actions
- unless read_inheritable_attribute(:hidden_actions)
- write_inheritable_attribute(:hidden_actions, ActionController::Base.public_instance_methods.map { |m| m.to_s })
- end
-
- read_inheritable_attribute(:hidden_actions)
+ read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, [])
end
# Hide each of the given methods from being callable as actions.
@@ -1199,7 +1195,7 @@ module ActionController #:nodoc:
end
def perform_action
- if self.class.action_methods.include?(action_name)
+ if action_methods.include?(action_name)
send(action_name)
default_render unless performed?
elsif respond_to? :method_missing
@@ -1208,7 +1204,7 @@ module ActionController #:nodoc:
elsif template_exists? && template_public?
default_render
else
- raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.to_a.sort.to_sentence}", caller
+ raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller
end
end
@@ -1234,7 +1230,15 @@ module ActionController #:nodoc:
end
def self.action_methods
- @action_methods ||= Set.new(public_instance_methods.map { |m| m.to_s }) - hidden_actions
+ @action_methods ||=
+ # All public instance methods of this class, including ancestors
+ public_instance_methods(true).map { |m| m.to_s }.to_set -
+ # Except for public instance methods of Base and its ancestors
+ Base.public_instance_methods(true).map { |m| m.to_s } +
+ # Be sure to include shadowed public instance methods of this class
+ public_instance_methods(false).map { |m| m.to_s } -
+ # And always exclude explicitly hidden actions
+ hidden_actions
end
def add_variables_to_assigns
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index f3535f8330..b27ec486c0 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -38,8 +38,8 @@ module ActionController #:nodoc:
# caches_action :show, :cache_path => { :project => 1 }, :expires_in => 1.hour
# caches_action :feed, :cache_path => Proc.new { |controller|
# controller.params[:user_id] ?
- # controller.send(:user_list_url, c.params[:user_id], c.params[:id]) :
- # controller.send(:list_url, c.params[:id]) }
+ # controller.send(:user_list_url, controller.params[:user_id], controller.params[:id]) :
+ # controller.send(:list_url, controller.params[:id]) }
# end
#
# If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb
index 8bc5e4c3a7..0ca27b30db 100644
--- a/actionpack/lib/action_controller/cgi_process.rb
+++ b/actionpack/lib/action_controller/cgi_process.rb
@@ -43,7 +43,7 @@ module ActionController #:nodoc:
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true
- } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
+ }
def initialize(cgi, session_options = {})
@cgi = cgi
@@ -61,53 +61,14 @@ module ActionController #:nodoc:
end
end
- # The request body is an IO input stream. If the RAW_POST_DATA environment
- # variable is already set, wrap it in a StringIO.
- def body
- if raw_post = env['RAW_POST_DATA']
- raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
- StringIO.new(raw_post)
- else
- @cgi.stdinput
- end
- end
-
- def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
- end
-
- def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
+ def body_stream #:nodoc:
+ @cgi.stdinput
end
def cookies
@cgi.cookies.freeze
end
- def host_with_port_without_standard_port_handling
- if forwarded = env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- elsif http_host = env['HTTP_HOST']
- http_host
- elsif server_name = env['SERVER_NAME']
- server_name
- else
- "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
- end
- end
-
- def host
- host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
- end
-
- def port
- if host_with_port_without_standard_port_handling =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
- end
- end
-
def session
unless defined?(@session)
if @session_options == false
diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb
index 10dc0cc45b..1d7236f18a 100644
--- a/actionpack/lib/action_controller/filters.rb
+++ b/actionpack/lib/action_controller/filters.rb
@@ -109,16 +109,17 @@ module ActionController #:nodoc:
update_options! options
end
+ # override these to return true in appropriate subclass
def before?
- self.class == BeforeFilter
+ false
end
def after?
- self.class == AfterFilter
+ false
end
def around?
- self.class == AroundFilter
+ false
end
# Make sets of strings from :only/:except options
@@ -170,6 +171,10 @@ module ActionController #:nodoc:
:around
end
+ def around?
+ true
+ end
+
def call(controller, &block)
if should_run_callback?(controller)
method = filter_responds_to_before_and_after? ? around_proc : self.method
@@ -212,6 +217,10 @@ module ActionController #:nodoc:
:before
end
+ def before?
+ true
+ end
+
def call(controller, &block)
super
if controller.send!(:performed?)
@@ -224,6 +233,10 @@ module ActionController #:nodoc:
def type
:after
end
+
+ def after?
+ true
+ end
end
# Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do
diff --git a/actionpack/lib/action_controller/headers.rb b/actionpack/lib/action_controller/headers.rb
index 7239438c49..139669c66f 100644
--- a/actionpack/lib/action_controller/headers.rb
+++ b/actionpack/lib/action_controller/headers.rb
@@ -1,31 +1,33 @@
+require 'active_support/memoizable'
+
module ActionController
module Http
class Headers < ::Hash
-
- def initialize(constructor = {})
- if constructor.is_a?(Hash)
+ extend ActiveSupport::Memoizable
+
+ def initialize(*args)
+ if args.size == 1 && args[0].is_a?(Hash)
super()
- update(constructor)
+ update(args[0])
else
- super(constructor)
+ super
end
end
-
+
def [](header_name)
if include?(header_name)
- super
+ super
else
- super(normalize_header(header_name))
+ super(env_name(header_name))
end
end
-
-
+
private
- # Takes an HTTP header name and returns it in the
- # format
- def normalize_header(header_name)
+ # Converts a HTTP header name to an environment variable name.
+ def env_name(header_name)
"HTTP_#{header_name.upcase.gsub(/-/, '_')}"
end
+ memoize :env_name
end
end
-end \ No newline at end of file
+end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 7e0a6b091e..dcbcf8bc1d 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -3,7 +3,7 @@ require 'action_controller/session/cookie_store'
module ActionController #:nodoc:
class RackRequest < AbstractRequest #:nodoc:
- attr_accessor :env, :session_options
+ attr_accessor :session_options
attr_reader :cgi
class SessionFixationAttempt < StandardError #:nodoc:
@@ -15,7 +15,7 @@ module ActionController #:nodoc:
:session_path => "/", # available to all paths in app
:session_key => "_session_id",
:cookie_only => true
- } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
+ }
def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
@session_options = session_options
@@ -30,35 +30,21 @@ module ActionController #:nodoc:
SERVER_NAME SERVER_PROTOCOL
HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING
- HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_HOST
+ HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM
HTTP_NEGOTIATE HTTP_PRAGMA HTTP_REFERER HTTP_USER_AGENT ].each do |env|
define_method(env.sub(/^HTTP_/n, '').downcase) do
@env[env]
end
end
- # The request body is an IO input stream. If the RAW_POST_DATA environment
- # variable is already set, wrap it in a StringIO.
- def body
- if raw_post = env['RAW_POST_DATA']
- StringIO.new(raw_post)
- else
- @env['rack.input']
- end
+ def body_stream #:nodoc:
+ @env['rack.input']
end
def key?(key)
@env.key?(key)
end
- def query_parameters
- @query_parameters ||= self.class.parse_query_parameters(query_string)
- end
-
- def request_parameters
- @request_parameters ||= parse_formatted_request_parameters
- end
-
def cookies
return {} unless @env["HTTP_COOKIE"]
@@ -70,34 +56,6 @@ module ActionController #:nodoc:
@env["rack.request.cookie_hash"]
end
- def host_with_port_without_standard_port_handling
- if forwarded = @env["HTTP_X_FORWARDED_HOST"]
- forwarded.split(/,\s?/).last
- elsif http_host = @env['HTTP_HOST']
- http_host
- elsif server_name = @env['SERVER_NAME']
- server_name
- else
- "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
- end
- end
-
- def host
- host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
- end
-
- def port
- if host_with_port_without_standard_port_handling =~ /:(\d+)$/
- $1.to_i
- else
- standard_port
- end
- end
-
- def remote_addr
- @env['REMOTE_ADDR']
- end
-
def server_port
@env['SERVER_PORT'].to_i
end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 60ff75fe2c..185518761d 100644
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -2,35 +2,35 @@ require 'tempfile'
require 'stringio'
require 'strscan'
-module ActionController
- # HTTP methods which are accepted by default.
- ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options ))
+require 'active_support/memoizable'
+module ActionController
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
+ extend ActiveSupport::Memoizable
+
def self.relative_url_root=(*args)
ActiveSupport::Deprecation.warn(
"ActionController::AbstractRequest.relative_url_root= has been renamed." +
"You can now set it with config.action_controller.relative_url_root=", caller)
end
- # The hash of CGI-like environment variables for this request, such as
- #
- # { 'SERVER_PROTOCOL' => 'HTTP/1.1', 'HTTP_ACCEPT_LANGUAGE' => 'en-us', ... }
+ HTTP_METHODS = %w(get head put post delete options)
+ HTTP_METHOD_LOOKUP = HTTP_METHODS.inject({}) { |h, m| h[m] = h[m.upcase] = m.to_sym; h }
+
+ # The hash of environment variables for this request,
+ # such as { 'RAILS_ENV' => 'production' }.
attr_reader :env
# The true HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# UnknownHttpMethod is raised for invalid methods not listed in ACCEPTED_HTTP_METHODS.
def request_method
- @request_method ||= begin
- method = ((@env['REQUEST_METHOD'] == 'POST' && !parameters[:_method].blank?) ? parameters[:_method].to_s : @env['REQUEST_METHOD']).downcase
- if ACCEPTED_HTTP_METHODS.include?(method)
- method.to_sym
- else
- raise UnknownHttpMethod, "#{method}, accepted HTTP methods are #{ACCEPTED_HTTP_METHODS.to_a.to_sentence}"
- end
- end
+ method = @env['REQUEST_METHOD']
+ method = parameters[:_method] if method == 'POST' && !parameters[:_method].blank?
+
+ HTTP_METHOD_LOOKUP[method] || raise(UnknownHttpMethod, "#{method}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}")
end
+ memoize :request_method
# The HTTP request \method as a lowercase symbol, such as <tt>:get</tt>.
# Note, HEAD is returned as <tt>:get</tt> since the two are functionally
@@ -69,34 +69,60 @@ module ActionController
#
# request.headers["Content-Type"] # => "text/plain"
def headers
- @headers ||= ActionController::Http::Headers.new(@env)
+ ActionController::Http::Headers.new(@env)
end
+ memoize :headers
# Returns the content length of the request as an integer.
def content_length
- @content_length ||= env['CONTENT_LENGTH'].to_i
+ @env['CONTENT_LENGTH'].to_i
end
+ memoize :content_length
# The MIME type of the HTTP request, such as Mime::XML.
#
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- @content_type ||= Mime::Type.lookup(content_type_without_parameters)
+ Mime::Type.lookup(content_type_without_parameters)
end
+ memoize :content_type
# Returns the accepted MIME type for the request.
def accepts
- @accepts ||=
- begin
- header = @env['HTTP_ACCEPT'].to_s.strip
+ header = @env['HTTP_ACCEPT'].to_s.strip
- if header.empty?
- [content_type, Mime::ALL].compact
- else
- Mime::Type.parse(header)
- end
- end
+ if header.empty?
+ [content_type, Mime::ALL].compact
+ else
+ Mime::Type.parse(header)
+ end
+ end
+ memoize :accepts
+
+ def if_modified_since
+ if since = env['HTTP_IF_MODIFIED_SINCE']
+ Time.rfc2822(since)
+ end
+ end
+ memoize :if_modified_since
+
+ def if_none_match
+ env['HTTP_IF_NONE_MATCH']
+ end
+
+ def not_modified?(modified_at)
+ if_modified_since && modified_at && if_modified_since >= modified_at
+ end
+
+ def etag_matches?(etag)
+ if_none_match && if_none_match == etag
+ end
+
+ # Check response freshness (Last-Modified and ETag) against request
+ # If-Modified-Since and If-None-Match conditions.
+ def fresh?(response)
+ not_modified?(response.last_modified) || etag_matches?(response.etag)
end
# Returns the Mime type for the \format used in the request.
@@ -105,7 +131,7 @@ module ActionController
# GET /posts/5.xhtml | request.format => Mime::HTML
# GET /posts/5 | request.format => Mime::HTML or MIME::JS, or request.accepts.first depending on the value of <tt>ActionController::Base.use_accept_header</tt>
def format
- @format ||= begin
+ @format ||=
if parameters[:format]
Mime::Type.lookup_by_extension(parameters[:format])
elsif ActionController::Base.use_accept_header
@@ -115,7 +141,6 @@ module ActionController
else
Mime::Type.lookup_by_extension("html")
end
- end
end
@@ -203,22 +228,26 @@ EOM
@env['REMOTE_ADDR']
end
+ memoize :remote_ip
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
end
+ memoize :server_software
# Returns the complete URL used for this request.
def url
protocol + host_with_port + request_uri
end
+ memoize :url
# Returns 'https://' if this is an SSL request and 'http://' otherwise.
def protocol
ssl? ? 'https://' : 'http://'
end
+ memoize :protocol
# Is this an SSL request?
def ssl?
@@ -226,19 +255,36 @@ EOM
end
# Returns the \host for this request, such as "example.com".
+ def raw_host_with_port
+ if forwarded = env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ else
+ env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ # Returns the host for this request, such as example.com.
def host
+ raw_host_with_port.sub(/:\d+$/, '')
end
+ memoize :host
# Returns a \host:\port string for this request, such as "example.com" or
# "example.com:8080".
def host_with_port
- @host_with_port ||= host + port_string
+ "#{host}#{port_string}"
end
+ memoize :host_with_port
# Returns the port number of this request as an integer.
def port
- @port_as_int ||= @env['SERVER_PORT'].to_i
+ if raw_host_with_port =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
end
+ memoize :port
# Returns the standard \port number for this request's protocol.
def standard_port
@@ -251,7 +297,7 @@ EOM
# Returns a \port suffix like ":8080" if the \port number of this request
# is not the default HTTP \port 80 or HTTPS \port 443.
def port_string
- (port == standard_port) ? '' : ":#{port}"
+ port == standard_port ? '' : ":#{port}"
end
# Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify
@@ -280,6 +326,7 @@ EOM
@env['QUERY_STRING'] || ''
end
end
+ memoize :query_string
# Returns the request URI, accounting for server idiosyncrasies.
# WEBrick includes the full URL. IIS leaves REQUEST_URI blank.
@@ -289,21 +336,23 @@ EOM
(%r{^\w+\://[^/]+(/.*|$)$} =~ uri) ? $1 : uri
else
# Construct IIS missing REQUEST_URI from SCRIPT_NAME and PATH_INFO.
- script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
- uri = @env['PATH_INFO']
- uri = uri.sub(/#{script_filename}\//, '') unless script_filename.nil?
- unless (env_qs = @env['QUERY_STRING']).nil? || env_qs.empty?
- uri << '?' << env_qs
+ uri = @env['PATH_INFO'].to_s
+
+ if script_filename = @env['SCRIPT_NAME'].to_s.match(%r{[^/]+$})
+ uri = uri.sub(/#{script_filename}\//, '')
end
- if uri.nil?
+ env_qs = @env['QUERY_STRING'].to_s
+ uri += "?#{env_qs}" unless env_qs.empty?
+
+ if uri.blank?
@env.delete('REQUEST_URI')
- uri
else
@env['REQUEST_URI'] = uri
end
end
end
+ memoize :request_uri
# Returns the interpreted \path to requested resource after all the installation
# directory of this application was taken into account.
@@ -314,6 +363,7 @@ EOM
path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '')
path || ''
end
+ memoize :path
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
@@ -350,19 +400,41 @@ EOM
@path_parameters ||= {}
end
+ # The request body is an IO input stream. If the RAW_POST_DATA environment
+ # variable is already set, wrap it in a StringIO.
+ def body
+ if raw_post = env['RAW_POST_DATA']
+ raw_post.force_encoding(Encoding::BINARY) if raw_post.respond_to?(:force_encoding)
+ StringIO.new(raw_post)
+ else
+ body_stream
+ end
+ end
- #--
- # Must be implemented in the concrete request
- #++
+ def remote_addr
+ @env['REMOTE_ADDR']
+ end
- # The request \body as an IO input stream.
- def body
+ def referrer
+ @env['HTTP_REFERER']
end
+ alias referer referrer
+
- def query_parameters #:nodoc:
+ def query_parameters
+ @query_parameters ||= self.class.parse_query_parameters(query_string)
end
- def request_parameters #:nodoc:
+ def request_parameters
+ @request_parameters ||= parse_formatted_request_parameters
+ end
+
+
+ #--
+ # Must be implemented in the concrete request
+ #++
+
+ def body_stream #:nodoc:
end
def cookies #:nodoc:
@@ -389,8 +461,9 @@ EOM
# The raw content type string with its parameters stripped off.
def content_type_without_parameters
- @content_type_without_parameters ||= self.class.extract_content_type_without_parameters(content_type_with_parameters)
+ self.class.extract_content_type_without_parameters(content_type_with_parameters)
end
+ memoize :content_type_without_parameters
private
def content_type_from_legacy_post_data_format_header
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index 77b329b70e..becf6b0b63 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -481,8 +481,7 @@ module ActionController
resource.collection_methods.each do |method, actions|
actions.each do |action|
action_options = action_options_for(action, resource, method)
- map.named_route("#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
- map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}.:format", action_options)
+ map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.plural}", "#{resource.path}#{resource.action_separator}#{action}", action_options)
end
end
end
@@ -495,18 +494,15 @@ module ActionController
index_route_name << "_index"
end
- map.named_route(index_route_name, resource.path, index_action_options)
- map.named_route("formatted_#{index_route_name}", "#{resource.path}.:format", index_action_options)
+ map_named_routes(map, index_route_name, resource.path, index_action_options)
create_action_options = action_options_for("create", resource)
- map.connect(resource.path, create_action_options)
- map.connect("#{resource.path}.:format", create_action_options)
+ map_unnamed_routes(map, resource.path, create_action_options)
end
def map_default_singleton_actions(map, resource)
create_action_options = action_options_for("create", resource)
- map.connect(resource.path, create_action_options)
- map.connect("#{resource.path}.:format", create_action_options)
+ map_unnamed_routes(map, resource.path, create_action_options)
end
def map_new_actions(map, resource)
@@ -514,11 +510,9 @@ module ActionController
actions.each do |action|
action_options = action_options_for(action, resource, method)
if action == :new
- map.named_route("new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
- map.named_route("formatted_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}.:format", action_options)
+ map_named_routes(map, "new_#{resource.name_prefix}#{resource.singular}", resource.new_path, action_options)
else
- map.named_route("#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
- map.named_route("formatted_#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}.:format", action_options)
+ map_named_routes(map, "#{action}_new_#{resource.name_prefix}#{resource.singular}", "#{resource.new_path}#{resource.action_separator}#{action}", action_options)
end
end
end
@@ -532,22 +526,28 @@ module ActionController
action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash)
action_path ||= Base.resources_path_names[action] || action
- map.named_route("#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
- map.named_route("formatted_#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}.:format",action_options)
+ map_named_routes(map, "#{action}_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}#{resource.action_separator}#{action_path}", action_options)
end
end
show_action_options = action_options_for("show", resource)
- map.named_route("#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
- map.named_route("formatted_#{resource.name_prefix}#{resource.singular}", "#{resource.member_path}.:format", show_action_options)
+ map_named_routes(map, "#{resource.name_prefix}#{resource.singular}", resource.member_path, show_action_options)
update_action_options = action_options_for("update", resource)
- map.connect(resource.member_path, update_action_options)
- map.connect("#{resource.member_path}.:format", update_action_options)
+ map_unnamed_routes(map, resource.member_path, update_action_options)
destroy_action_options = action_options_for("destroy", resource)
- map.connect(resource.member_path, destroy_action_options)
- map.connect("#{resource.member_path}.:format", destroy_action_options)
+ map_unnamed_routes(map, resource.member_path, destroy_action_options)
+ end
+
+ def map_unnamed_routes(map, path_without_format, options)
+ map.connect(path_without_format, options)
+ map.connect("#{path_without_format}.:format", options)
+ end
+
+ def map_named_routes(map, name, path_without_format, options)
+ map.named_route(name, path_without_format, options)
+ map.named_route("formatted_#{name}", "#{path_without_format}.:format", options)
end
def add_conditions_for(conditions, method)
@@ -574,4 +574,4 @@ end
class ActionController::Routing::RouteSet::Mapper
include ActionController::Resources
-end
+end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb
index da352b6993..a85fad0d39 100644
--- a/actionpack/lib/action_controller/response.rb
+++ b/actionpack/lib/action_controller/response.rb
@@ -37,12 +37,20 @@ module ActionController # :nodoc:
attr_accessor :body
# The headers of the response, as a Hash. It maps header names to header values.
attr_accessor :headers
- attr_accessor :session, :cookies, :assigns, :template, :redirected_to, :redirected_to_method_params, :layout
+ attr_accessor :session, :cookies, :assigns, :template, :layout
+ attr_accessor :redirected_to, :redirected_to_method_params
def initialize
@body, @headers, @session, @assigns = "", DEFAULT_HEADERS.merge("cookie" => []), [], []
end
+ def status; headers['Status'] end
+ def status=(status) headers['Status'] = status end
+
+ def location; headers['Location'] end
+ def location=(url) headers['Location'] = url end
+
+
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
#
@@ -70,35 +78,29 @@ module ActionController # :nodoc:
charset.blank? ? nil : charset.strip.split("=")[1]
end
- def redirect(to_url, response_status)
- self.headers["Status"] = response_status
- self.headers["Location"] = to_url
+ def last_modified
+ Time.rfc2822(headers['Last-Modified'])
+ end
- self.body = "<html><body>You are being <a href=\"#{to_url}\">redirected</a>.</body></html>"
+ def last_modified=(utc_time)
+ headers['Last-Modified'] = utc_time.httpdate
end
- def prepare!
- handle_conditional_get!
- convert_content_type!
- set_content_length!
+ def etag; headers['ETag'] end
+ def etag=(etag)
+ headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
end
- # Sets the Last-Modified response header. Returns whether it's older than
- # the If-Modified-Since request header.
- def last_modified!(utc_time)
- headers['Last-Modified'] ||= utc_time.httpdate
- if request && since = request.headers['HTTP_IF_MODIFIED_SINCE']
- utc_time <= Time.rfc2822(since)
- end
+ def redirect(url, status)
+ self.status = status
+ self.location = url
+ self.body = "<html><body>You are being <a href=\"#{url}\">redirected</a>.</body></html>"
end
- # Sets the ETag response header. Returns whether it matches the
- # If-None-Match request header.
- def etag!(tag)
- headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}")
- if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag']
- true
- end
+ def prepare!
+ handle_conditional_get!
+ convert_content_type!
+ set_content_length!
end
private
@@ -106,15 +108,15 @@ module ActionController # :nodoc:
if nonempty_ok_response?
set_conditional_cache_control!
- if etag!(body)
- headers['Status'] = '304 Not Modified'
+ self.etag ||= body
+ if request && request.etag_matches?(etag)
+ self.status = '304 Not Modified'
self.body = ''
end
end
end
def nonempty_ok_response?
- status = headers['Status']
ok = !status || status[0..2] == '200'
ok && body.is_a?(String) && !body.empty?
end
diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb
index 75784c3b78..9d4b740a44 100644
--- a/actionpack/lib/action_controller/routing/segments.rb
+++ b/actionpack/lib/action_controller/routing/segments.rb
@@ -2,7 +2,8 @@ module ActionController
module Routing
class Segment #:nodoc:
RESERVED_PCHAR = ':@&=+$,;'
- UNSAFE_PCHAR = Regexp.new("[^#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}]", false, 'N').freeze
+ SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}"
+ UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze
# TODO: Convert :is_optional accessor to read only
attr_accessor :is_optional
diff --git a/actionpack/lib/action_controller/session/cookie_store.rb b/actionpack/lib/action_controller/session/cookie_store.rb
index b477c1f7da..5bf7503f04 100644
--- a/actionpack/lib/action_controller/session/cookie_store.rb
+++ b/actionpack/lib/action_controller/session/cookie_store.rb
@@ -129,7 +129,7 @@ class CGI::Session::CookieStore
private
# Marshal a session hash into safe cookie data. Include an integrity hash.
def marshal(session)
- data = ActiveSupport::Base64.encode64(Marshal.dump(session)).chop
+ data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
"#{data}--#{generate_digest(data)}"
end
diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb
index 66675aaa13..0c705207e3 100644
--- a/actionpack/lib/action_controller/test_process.rb
+++ b/actionpack/lib/action_controller/test_process.rb
@@ -23,7 +23,7 @@ module ActionController #:nodoc:
class TestRequest < AbstractRequest #:nodoc:
attr_accessor :cookies, :session_options
- attr_accessor :query_parameters, :request_parameters, :path, :session, :env
+ attr_accessor :query_parameters, :request_parameters, :path, :session
attr_accessor :host, :user_agent
def initialize(query_parameters = nil, request_parameters = nil, session = nil)
@@ -42,7 +42,7 @@ module ActionController #:nodoc:
end
# Wraps raw_post in a StringIO.
- def body
+ def body_stream #:nodoc:
StringIO.new(raw_post)
end
@@ -54,7 +54,7 @@ module ActionController #:nodoc:
def port=(number)
@env["SERVER_PORT"] = number.to_i
- @port_as_int = nil
+ port(true)
end
def action=(action_name)
@@ -68,6 +68,8 @@ module ActionController #:nodoc:
@env["REQUEST_URI"] = value
@request_uri = nil
@path = nil
+ request_uri(true)
+ path(true)
end
def request_uri=(uri)
@@ -77,21 +79,26 @@ module ActionController #:nodoc:
def accept=(mime_types)
@env["HTTP_ACCEPT"] = Array(mime_types).collect { |mime_types| mime_types.to_s }.join(",")
+ accepts(true)
end
- def remote_addr=(addr)
- @env['REMOTE_ADDR'] = addr
+ def if_modified_since=(last_modified)
+ @env["HTTP_IF_MODIFIED_SINCE"] = last_modified
end
- def remote_addr
- @env['REMOTE_ADDR']
+ def if_none_match=(etag)
+ @env["HTTP_IF_NONE_MATCH"] = etag
end
- def request_uri
+ def remote_addr=(addr)
+ @env['REMOTE_ADDR'] = addr
+ end
+
+ def request_uri(*args)
@request_uri || super
end
- def path
+ def path(*args)
@path || super
end
@@ -113,17 +120,13 @@ module ActionController #:nodoc:
end
end
@parameters = nil # reset TestRequest#parameters to use the new path_parameters
- end
-
+ end
+
def recycle!
self.request_parameters = {}
self.query_parameters = {}
self.path_parameters = {}
- @request_method, @accepts, @content_type = nil, nil, nil
- end
-
- def referer
- @env["HTTP_REFERER"]
+ unmemoize_all
end
private
@@ -448,10 +451,13 @@ module ActionController #:nodoc:
end
def method_missing(selector, *args)
- return @controller.send!(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
- return super
+ if ActionController::Routing::Routes.named_routes.helpers.include?(selector)
+ @controller.send(selector, *args)
+ else
+ super
+ end
end
-
+
# Shortcut for <tt>ActionController::TestUploadedFile.new(Test::Unit::TestCase.fixture_path + path, type)</tt>:
#
# post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb
index bdcb1dc246..ad59d92086 100644
--- a/actionpack/lib/action_view/base.rb
+++ b/actionpack/lib/action_view/base.rb
@@ -300,6 +300,8 @@ module ActionView #:nodoc:
# # => 'users/legacy.rhtml'
#
def pick_template(template_path)
+ return template_path if template_path.respond_to?(:render)
+
path = template_path.sub(/^\//, '')
if m = path.match(/(.*)\.(\w+)$/)
template_file_name, template_file_extension = m[1], m[2]
@@ -343,7 +345,8 @@ module ActionView #:nodoc:
ActiveSupport::Deprecation.warn("use_full_path option has been deprecated and has no affect.", caller)
end
- if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/")
+ if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) &&
+ template_path.is_a?(String) && !template_path.include?("/")
raise ActionViewError, <<-END_ERROR
Due to changes in ActionMailer, you need to provide the mailer_name along with the template name.
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index 769eada120..c2b4f51c9c 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -463,7 +463,7 @@ module ActionView
end
private
- COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!.threadsafe!
+ COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!
# 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
@@ -618,6 +618,11 @@ module ActionView
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
+
+ # Set mtime to the latest of the combined files to allow for
+ # consistent ETag without a shared filesystem.
+ mt = asset_paths.map { |p| File.mtime(File.join(ASSETS_DIR, p)) }.max
+ File.utime(mt, mt, joined_asset_path)
end
def collect_asset_files(*path)
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index c7a1d40ff2..953a2a9f86 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -13,9 +13,6 @@ module ActionView
# the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of
# "date[month]".
module DateHelper
- include ActionView::Helpers::TagHelper
- DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
-
# Reports the approximate distance in time between two Time or Date objects or integers as seconds.
# Set <tt>include_seconds</tt> to true if you want more detailed approximations when distance < 1 min, 29 secs
# Distances are reported based on the following table:
@@ -52,7 +49,7 @@ module ActionView
# distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute
# distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute
# distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year
- # distance_of_time_in_words(from_time, from_time + 4.years + 15.days + 30.minutes + 5.seconds) # => over 4 years
+ # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => over 4 years
#
# to_time = Time.now + 6.years + 19.days
# distance_of_time_in_words(from_time, to_time, true) # => over 6 years
@@ -109,19 +106,36 @@ module ActionView
alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
# Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based
- # attribute (identified by +method+) on an object assigned to the template (identified by +object+). It's
- # possible to tailor the selects through the +options+ hash, which accepts all the keys that each of the
- # individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of discard
- # options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set
- # to true, they'll drop the respective select. Discarding the month select will also automatically discard the
- # day select. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an
- # array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. Symbols may be omitted
- # and the respective select is not included.
- #
- # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>,
- # <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>.
- #
- # Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change.
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). You can
+ # the output in the +options+ hash.
+ #
+ # ==== Options
+ # * <tt>:use_month_numbers</tt> - Set to true if you want to use month numbers rather than month names (e.g.
+ # "2" instead of "February").
+ # * <tt>:use_short_month</tt> - Set to true if you want to use the abbreviated month name instead of the full
+ # name (e.g. "Feb" instead of "February").
+ # * <tt>:add_month_number</tt> - Set to true if you want to show both, the month's number and name (e.g.
+ # "2 - February" instead of "February").
+ # * <tt>:use_month_names</tt> - Set to an array with 12 month names if you want to customize month names.
+ # Note: You can also use Rails' new i18n functionality for this.
+ # * <tt>:date_separator</tt> - Specifies a string to separate the date fields. Default is "" (i.e. nothing).
+ # * <tt>:start_year</tt> - Set the start year for the year select. Default is <tt>Time.now.year - 5</tt>.
+ # * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Time.now.year + 5</tt>.
+ # * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
+ # as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
+ # first of the given month in order to not create invalid dates like 31 February.
+ # * <tt>:discard_month</tt> - Set to true if you don't want to show a month select. This includes the month
+ # as a hidden field instead of showing a select field. Also note that this implicitly sets :discard_day to true.
+ # * <tt>:discard_year</tt> - Set to true if you don't want to show a year select. This includes the year
+ # as a hidden field instead of showing a select field.
+ # * <tt>:order</tt> - Set to an array containing <tt>:day</tt>, <tt>:month</tt> and <tt>:year</tt> do
+ # customize the order in which the select fields are shown. If you leave out any of the symbols, the respective
+ # select will not be shown (like when you set <tt>:discard_xxx => true</tt>. Defaults to the order defined in
+ # the respective locale (e.g. [:year, :month, :day] in the en-US locale that ships with Rails).
+ # * <tt>:include_blank</tt> - Include a blank option in every select field so it's possible to set empty
+ # dates.
+ # * <tt>:default</tt> - Set a default date if the affected date isn't set or is nil.
+ # * <tt>:disabled</tt> - Set to true if you want show the select fields as disabled.
#
# If anything is passed in the +html_options+ hash it will be applied to every select tag in the set.
#
@@ -165,9 +179,9 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options)
end
- # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a specified
- # time-based attribute (identified by +method+) on an object assigned to the template (identified by +object+).
- # You can include the seconds with <tt>:include_seconds</tt>.
+ # Returns a set of select tags (one for hour, minute and optionally second) pre-selected for accessing a
+ # specified time-based attribute (identified by +method+) on an object assigned to the template (identified by
+ # +object+). You can include the seconds with <tt>:include_seconds</tt>.
#
# This method will also generate 3 input hidden tags, for the actual year, month and day unless the option
# <tt>:ignore_date</tt> is set to +true+.
@@ -178,7 +192,8 @@ module ActionView
# # Creates a time select tag that, when POSTed, will be stored in the post variable in the sunrise attribute
# time_select("post", "sunrise")
#
- # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted attribute
+ # # Creates a time select tag that, when POSTed, will be stored in the order variable in the submitted
+ # # attribute
# time_select("order", "submitted")
#
# # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute
@@ -210,7 +225,8 @@ module ActionView
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
# ==== Examples
- # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute
+ # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on
+ # # attribute
# datetime_select("post", "written_on")
#
# # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the
@@ -230,12 +246,12 @@ module ActionView
InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options)
end
- # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
- # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
- # it will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt>,
- # <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to control visual display of
- # the elements.
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the
+ # +datetime+. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with
+ # an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not
+ # supply a Symbol, it will be appended onto the <tt>:order</tt> passed in. You can also add
+ # <tt>:date_separator</tt>, <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to
+ # control visual display of the elements.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -270,14 +286,13 @@ module ActionView
# select_datetime(my_date_time, :prefix => 'payday')
#
def select_datetime(datetime = Time.current, options = {}, html_options = {})
- separator = options[:datetime_separator] || ''
- select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options)
+ DateTimeSelector.new(datetime, options, html_options).select_datetime
end
# Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
# It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of
- # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it
- # will be appended onto the <tt>:order</tt> passed in.
+ # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol,
+ # it will be appended onto the <tt>:order</tt> passed in.
#
# If anything is passed in the html_options hash it will be applied to every select tag in the set.
#
@@ -307,12 +322,7 @@ module ActionView
# select_date(my_date, :prefix => 'payday')
#
def select_date(date = Date.current, options = {}, html_options = {})
- options.reverse_merge!(:order => [], :date_separator => '')
- [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) }
-
- options[:order].inject([]) { |s, o|
- s << self.send("select_#{o}", date, options, html_options)
- }.join(options[:date_separator])
+ DateTimeSelector.new(date, options, html_options).select_date
end
# Returns a set of html select-tags (one for hour and minute)
@@ -343,9 +353,7 @@ module ActionView
# select_time(my_time, :time_separator => ':', :include_seconds => true)
#
def select_time(datetime = Time.current, options = {}, html_options = {})
- separator = options[:time_separator] || ''
- select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) +
- (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '')
+ DateTimeSelector.new(datetime, options, html_options).select_time
end
# Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
@@ -366,15 +374,12 @@ module ActionView
# select_second(my_time, :field_name => 'interval')
#
def select_second(datetime, options = {}, html_options = {})
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : ''
- options[:use_hidden] ?
- (options[:include_seconds] ? _date_hidden_html(options[:field_name] || 'second', val, options) : '') :
- _date_select_html(options[:field_name] || 'second', _date_build_options(val), options, html_options)
+ DateTimeSelector.new(datetime, options, html_options).select_second
end
# Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
- # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute selected
- # The <tt>minute</tt> can also be substituted for a minute number.
+ # Also can return a select tag with options by <tt>minute_step</tt> from 0 through 59 with the 00 minute
+ # selected. The <tt>minute</tt> can also be substituted for a minute number.
# Override the field name using the <tt>:field_name</tt> option, 'minute' by default.
#
# ==== Examples
@@ -391,11 +396,7 @@ module ActionView
# select_minute(my_time, :field_name => 'stride')
#
def select_minute(datetime, options = {}, html_options = {})
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : ''
- options[:use_hidden] ?
- _date_hidden_html(options[:field_name] || 'minute', val, options) :
- _date_select_html(options[:field_name] || 'minute',
- _date_build_options(val, :step => options[:minute_step]), options, html_options)
+ DateTimeSelector.new(datetime, options, html_options).select_minute
end
# Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
@@ -416,9 +417,7 @@ module ActionView
# select_minute(my_time, :field_name => 'stride')
#
def select_hour(datetime, options = {}, html_options = {})
- val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : ''
- options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'hour', val, options) :
- _date_select_html(options[:field_name] || 'hour', _date_build_options(val, :end => 23), options, html_options)
+ DateTimeSelector.new(datetime, options, html_options).select_hour
end
# Returns a select tag with options for each of the days 1 through 31 with the current day selected.
@@ -439,11 +438,7 @@ module ActionView
# select_day(my_time, :field_name => 'due')
#
def select_day(date, options = {}, html_options = {})
- val = date ? (date.kind_of?(Fixnum) ? date : date.day) : ''
- options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'day', val, options) :
- _date_select_html(options[:field_name] || 'day',
- _date_build_options(val, :start => 1, :end => 31, :leading_zeros => false),
- options, html_options)
+ DateTimeSelector.new(date, options, html_options).select_day
end
# Returns a select tag with options for each of the months January through December with the current month
@@ -481,36 +476,7 @@ module ActionView
# select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...))
#
def select_month(date, options = {}, html_options = {})
- locale = options[:locale]
-
- val = date ? (date.kind_of?(Fixnum) ? date : date.month) : ''
- if options[:use_hidden]
- _date_hidden_html(options[:field_name] || 'month', val, options)
- else
- month_options = []
- month_names = options[:use_month_names] || begin
- key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
- I18n.translate key, :locale => locale
- end
- month_names.unshift(nil) if month_names.size < 13
-
- 1.upto(12) do |month_number|
- month_name = if options[:use_month_numbers]
- month_number
- elsif options[:add_month_numbers]
- month_number.to_s + ' - ' + month_names[month_number]
- else
- month_names[month_number]
- end
-
- month_options << ((val == month_number) ?
- content_tag(:option, month_name, :value => month_number, :selected => "selected") :
- content_tag(:option, month_name, :value => month_number)
- )
- month_options << "\n"
- end
- _date_select_html(options[:field_name] || 'month', month_options.join, options, html_options)
- end
+ DateTimeSelector.new(date, options, html_options).select_month
end
# Returns a select tag with options for each of the five years on each side of the current, which is selected.
@@ -537,158 +503,369 @@ module ActionView
# select_year(2006, :start_year => 2000, :end_year => 2010)
#
def select_year(date, options = {}, html_options = {})
- if !date || date == 0
+ DateTimeSelector.new(date, options, html_options).select_year
+ end
+ end
+
+ class DateTimeSelector #:nodoc:
+ extend ActiveSupport::Memoizable
+ include ActionView::Helpers::TagHelper
+
+ DEFAULT_PREFIX = 'date'.freeze unless const_defined?('DEFAULT_PREFIX')
+ POSITION = {
+ :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6
+ }.freeze unless const_defined?('POSITION')
+
+ def initialize(datetime, options = {}, html_options = {})
+ @options = options.dup
+ @html_options = html_options.dup
+ @datetime = datetime
+ end
+
+ def select_datetime
+ # TODO: Remove tag conditional
+ # Ideally we could just join select_date and select_date for the tag case
+ if @options[:tag] && @options[:ignore_date]
+ select_time
+ elsif @options[:tag]
+ order = date_order.dup
+ order -= [:hour, :minute, :second]
+
+ @options[:discard_year] ||= true unless order.include?(:year)
+ @options[:discard_month] ||= true unless order.include?(:month)
+ @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
+ @options[:discard_minute] ||= true if @options[:discard_hour]
+ @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute]
+
+ # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
+ # valid (otherwise it could be 31 and february wouldn't be a valid date)
+ if @options[:discard_day] && !@options[:discard_month]
+ @datetime = @datetime.change(:day => 1)
+ end
+
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
+ order += [:hour, :minute, :second] unless @options[:discard_hour]
+
+ build_selects_from_types(order)
+ else
+ "#{select_date}#{@options[:datetime_separator]}#{select_time}"
+ end
+ end
+
+ def select_date
+ order = date_order.dup
+
+ # TODO: Remove tag conditional
+ if @options[:tag]
+ @options[:discard_hour] = true
+ @options[:discard_minute] = true
+ @options[:discard_second] = true
+
+ @options[:discard_year] ||= true unless order.include?(:year)
+ @options[:discard_month] ||= true unless order.include?(:month)
+ @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day)
+
+ # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are
+ # valid (otherwise it could be 31 and february wouldn't be a valid date)
+ if @options[:discard_day] && !@options[:discard_month]
+ @datetime = @datetime.change(:day => 1)
+ end
+ end
+
+ [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
+
+ build_selects_from_types(order)
+ end
+
+ def select_time
+ order = []
+
+ # TODO: Remove tag conditional
+ if @options[:tag]
+ @options[:discard_month] = true
+ @options[:discard_year] = true
+ @options[:discard_day] = true
+ @options[:discard_second] ||= true unless @options[:include_seconds]
+
+ order += [:year, :month, :day] unless @options[:ignore_date]
+ end
+
+ order += [:hour, :minute]
+ order << :second if @options[:include_seconds]
+
+ build_selects_from_types(order)
+ end
+
+ def select_second
+ if @options[:use_hidden] || @options[:discard_second]
+ build_hidden(:second, sec) if @options[:include_seconds]
+ else
+ build_options_and_select(:second, sec)
+ end
+ end
+
+ def select_minute
+ if @options[:use_hidden] || @options[:discard_minute]
+ build_hidden(:minute, min)
+ else
+ build_options_and_select(:minute, min, :step => @options[:minute_step])
+ end
+ end
+
+ def select_hour
+ if @options[:use_hidden] || @options[:discard_hour]
+ build_hidden(:hour, hour)
+ else
+ build_options_and_select(:hour, hour, :end => 23)
+ end
+ end
+
+ def select_day
+ if @options[:use_hidden] || @options[:discard_day]
+ build_hidden(:day, day)
+ else
+ build_options_and_select(:day, day, :start => 1, :end => 31, :leading_zeros => false)
+ end
+ end
+
+ def select_month
+ if @options[:use_hidden] || @options[:discard_month]
+ build_hidden(:month, month)
+ else
+ month_options = []
+ 1.upto(12) do |month_number|
+ options = { :value => month_number }
+ options[:selected] = "selected" if month == month_number
+ month_options << content_tag(:option, month_name(month_number), options) + "\n"
+ end
+ build_select(:month, month_options.join)
+ end
+ end
+
+ def select_year
+ if !@datetime || @datetime == 0
val = ''
middle_year = Date.today.year
- elsif date.kind_of?(Fixnum)
- val = middle_year = date
else
- val = middle_year = date.year
+ val = middle_year = year
end
- if options[:use_hidden]
- _date_hidden_html(options[:field_name] || 'year', val, options)
+ if @options[:use_hidden] || @options[:discard_year]
+ build_hidden(:year, val)
else
- options[:start_year] ||= middle_year - 5
- options[:end_year] ||= middle_year + 5
- step = options[:start_year] < options[:end_year] ? 1 : -1
-
- _date_select_html(options[:field_name] || 'year',
- _date_build_options(val,
- :start => options[:start_year],
- :end => options[:end_year],
- :step => step,
- :leading_zeros => false
- ), options, html_options)
+ options = {}
+ options[:start] = @options[:start_year] || middle_year - 5
+ options[:end] = @options[:end_year] || middle_year + 5
+ options[:step] = options[:start] < options[:end] ? 1 : -1
+ options[:leading_zeros] = false
+
+ build_options_and_select(:year, val, options)
end
end
private
- def _date_build_options(selected, options={})
- options.reverse_merge!(:start => 0, :end => 59, :step => 1, :leading_zeros => true)
+ %w( sec min hour day month year ).each do |method|
+ define_method(method) do
+ @datetime.kind_of?(Fixnum) ? @datetime : @datetime.send(method) if @datetime
+ end
+ end
+
+ # Returns translated month names, but also ensures that a custom month
+ # name array has a leading nil element
+ def month_names
+ month_names = @options[:use_month_names] || translated_month_names
+ month_names.unshift(nil) if month_names.size < 13
+ month_names
+ end
+ memoize :month_names
+
+ # Returns translated month names
+ # => [nil, "January", "February", "March",
+ # "April", "May", "June", "July",
+ # "August", "September", "October",
+ # "November", "December"]
+ #
+ # If :use_short_month option is set
+ # => [nil, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ # "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+ def translated_month_names
+ begin
+ key = @options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names'
+ I18n.translate(key, :locale => @options[:locale])
+ end
+ end
+
+ # Lookup month name for number
+ # month_name(1) => "January"
+ #
+ # If :use_month_numbers option is passed
+ # month_name(1) => 1
+ #
+ # If :add_month_numbers option is passed
+ # month_name(1) => "1 - January"
+ def month_name(number)
+ if @options[:use_month_numbers]
+ number
+ elsif @options[:add_month_numbers]
+ "#{number} - #{month_names[number]}"
+ else
+ month_names[number]
+ end
+ end
+
+ def date_order
+ @options[:order] || translated_date_order
+ end
+ memoize :date_order
+
+ def translated_date_order
+ begin
+ I18n.translate(:'date.order', :locale => @options[:locale]) || []
+ end
+ end
+
+ # Build full select tag from date type and options
+ def build_options_and_select(type, selected, options = {})
+ build_select(type, build_options(selected, options))
+ end
+
+ # Build select option html from date value and options
+ # build_options(15, :start => 1, :end => 31)
+ # => "<option value="1">1</option>
+ # <option value=\"2\">2</option>
+ # <option value=\"3\">3</option>..."
+ def build_options(selected, options = {})
+ start = options.delete(:start) || 0
+ stop = options.delete(:end) || 59
+ step = options.delete(:step) || 1
+ leading_zeros = options.delete(:leading_zeros).nil? ? true : false
select_options = []
- (options[:start] || 0).step((options[:end] || 59), options[:step] || 1) do |i|
- value = options[:leading_zeros] ? sprintf("%02d", i) : i
+ start.step(stop, step) do |i|
+ value = leading_zeros ? sprintf("%02d", i) : i
tag_options = { :value => value }
tag_options[:selected] = "selected" if selected == i
-
select_options << content_tag(:option, value, tag_options)
end
select_options.join("\n") + "\n"
end
- def _date_select_html(type, html_options, options, select_tag_options = {})
- _date_name_and_id_from_options(options, type)
- select_options = {:id => options[:id], :name => options[:name]}
- select_options.merge!(:disabled => 'disabled') if options[:disabled]
- select_options.merge!(select_tag_options) unless select_tag_options.empty?
+ # Builds select tag from date type and html select options
+ # build_select(:month, "<option value="1">January</option>...")
+ # => "<select id="post_written_on_2i" name="post[written_on(2i)]">
+ # <option value="1">January</option>...
+ # </select>"
+ def build_select(type, select_options_as_html)
+ select_options = {
+ :id => input_id_from_type(type),
+ :name => input_name_from_type(type)
+ }.merge(@html_options)
+ select_options.merge!(:disabled => 'disabled') if @options[:disabled]
+
select_html = "\n"
- select_html << content_tag(:option, '', :value => '') + "\n" if options[:include_blank]
- select_html << html_options.to_s
+ select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank]
+ select_html << select_options_as_html.to_s
+
content_tag(:select, select_html, select_options) + "\n"
end
- def _date_hidden_html(type, value, options)
- _date_name_and_id_from_options(options, type)
- hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n"
+ # Builds hidden input tag for date part and value
+ # build_hidden(:year, 2008)
+ # => "<input id="post_written_on_1i" name="post[written_on(1i)]" type="hidden" value="2008" />"
+ def build_hidden(type, value)
+ tag(:input, {
+ :type => "hidden",
+ :id => input_id_from_type(type),
+ :name => input_name_from_type(type),
+ :value => value
+ }) + "\n"
end
- def _date_name_and_id_from_options(options, type)
- options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]")
- options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ # Returns the name attribute for the input tag
+ # => post[written_on(1i)]
+ def input_name_from_type(type)
+ prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
+ prefix += "[#{@options[:index]}]" if @options[:index]
+
+ field_name = @options[:field_name] || type
+ if @options[:include_position]
+ field_name += "(#{ActionView::Helpers::DateTimeSelector::POSITION[type]}i)"
+ end
+
+ @options[:discard_type] ? prefix : "#{prefix}[#{field_name}]"
+ end
+
+ # Returns the id attribute for the input tag
+ # => "post_written_on_1i"
+ def input_id_from_type(type)
+ input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '')
+ end
+
+ # Given an ordering of datetime components, create the selection html
+ # and join them with their appropriate seperators
+ def build_selects_from_types(order)
+ select = ''
+ order.reverse.each do |type|
+ separator = separator(type) unless type == order.first # don't add on last field
+ select.insert(0, separator.to_s + send("select_#{type}").to_s)
+ end
+ select
+ end
+
+ # Returns the separator for a given datetime component
+ def separator(type)
+ case type
+ when :month, :day
+ @options[:date_separator]
+ when :hour
+ (@options[:discard_year] && @options[:discard_day]) ? "" : @options[:datetime_separator]
+ when :minute
+ @options[:time_separator]
+ when :second
+ @options[:include_seconds] ? @options[:time_separator] : ""
+ end
end
end
class InstanceTag #:nodoc:
- include DateHelper
-
def to_date_select_tag(options = {}, html_options = {})
- date_or_time_select(options.merge(:discard_hour => true), html_options)
+ datetime_selector(options, html_options).select_date
end
def to_time_select_tag(options = {}, html_options = {})
- date_or_time_select(options.merge(:discard_year => true, :discard_month => true), html_options)
+ datetime_selector(options, html_options).select_time
end
def to_datetime_select_tag(options = {}, html_options = {})
- date_or_time_select(options, html_options)
+ datetime_selector(options, html_options).select_datetime
end
private
- def date_or_time_select(options, html_options = {})
- locale = options[:locale]
-
- defaults = { :discard_type => true }
- options = defaults.merge(options)
- datetime = value(object)
- datetime ||= default_time_from_options(options[:default]) unless options[:include_blank]
-
- position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
-
- order = options[:order] ||= I18n.translate(:'date.order', :locale => locale)
-
- # Discard explicit and implicit by not being included in the :order
- discard = {}
- discard[:year] = true if options[:discard_year] or !order.include?(:year)
- discard[:month] = true if options[:discard_month] or !order.include?(:month)
- discard[:day] = true if options[:discard_day] or discard[:month] or !order.include?(:day)
- discard[:hour] = true if options[:discard_hour]
- discard[:minute] = true if options[:discard_minute] or discard[:hour]
- discard[:second] = true unless options[:include_seconds] && !discard[:minute]
-
- # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are valid
- # (otherwise it could be 31 and february wouldn't be a valid date)
- if datetime && discard[:day] && !discard[:month]
- datetime = datetime.change(:day => 1)
- end
-
- # Maintain valid dates by including hidden fields for discarded elements
- [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) }
-
- # Ensure proper ordering of :hour, :minute and :second
- [:hour, :minute, :second].each { |o| order.delete(o); order.push(o) }
-
- date_or_time_select = ''
- order.reverse.each do |param|
- # Send hidden fields for discarded elements once output has started
- # This ensures AR can reconstruct valid dates using ParseDate
- next if discard[param] && (date_or_time_select.empty? || options[:ignore_date])
-
- date_or_time_select.insert(0,
- self.send("select_#{param}",
- datetime,
- options_with_prefix(position[param], options.merge(:use_hidden => discard[param])),
- html_options))
- date_or_time_select.insert(0,
- case param
- when :hour then (discard[:year] && discard[:day] ? "" : " &mdash; ")
- when :minute then " : "
- when :second then options[:include_seconds] ? " : " : ""
- else ""
- end)
- end
-
- date_or_time_select
+ def datetime_selector(options, html_options)
+ datetime = value(object) || default_datetime(options)
+
+ options = options.dup
+ options[:field_name] = @method_name
+ options[:include_position] = true
+ options[:prefix] ||= @object_name
+ options[:index] ||= @auto_index
+ options[:datetime_separator] ||= ' &mdash; '
+ options[:time_separator] ||= ' : '
+
+ DateTimeSelector.new(datetime, options.merge(:tag => true), html_options)
end
- def options_with_prefix(position, options)
- prefix = "#{@object_name}"
- if options[:index]
- prefix << "[#{options[:index]}]"
- elsif @auto_index
- prefix << "[#{@auto_index}]"
- end
- options.merge(:prefix => "#{prefix}[#{@method_name}(#{position}i)]")
- end
+ def default_datetime(options)
+ return if options[:include_blank]
- def default_time_from_options(default)
- case default
+ case options[:default]
when nil
Time.current
when Date, Time
- default
+ options[:default]
else
+ default = options[:default].dup
+
# Rename :minute and :second to :min and :sec
default[:min] ||= default[:minute]
default[:sec] ||= default[:second]
@@ -699,8 +876,11 @@ module ActionView
default[key] ||= time.send(key)
end
- Time.utc_time(default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec])
- end
+ Time.utc_time(
+ default[:year], default[:month], default[:day],
+ default[:hour], default[:min], default[:sec]
+ )
+ end
end
end
diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb
index e8ca02d760..e5777b71d7 100644
--- a/actionpack/lib/action_view/helpers/form_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb
@@ -155,10 +155,10 @@ module ActionView
# Creates a file upload field. If you are using file uploads then you will also need
# to set the multipart option for the form tag:
#
- # <%= form_tag { :action => "post" }, { :multipart => true } %>
+ # <% form_tag '/upload', :multipart => true do %>
# <label for="file">File to Upload</label> <%= file_field_tag "file" %>
# <%= submit_tag %>
- # <%= end_form_tag %>
+ # <% end %>
#
# The specified URL will then be passed a File object containing the selected file, or if the field
# was left blank, a StringIO object.
diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb
index 8c1dea2186..77f19b36a6 100644
--- a/actionpack/lib/action_view/helpers/number_helper.rb
+++ b/actionpack/lib/action_view/helpers/number_helper.rb
@@ -71,9 +71,9 @@ module ActionView
def number_to_currency(number, options = {})
options.symbolize_keys!
- defaults, currency = I18n.translate([:'number.format', :'number.currency.format'],
- :locale => options[:locale]) || [{},{}]
- defaults = defaults.merge(currency)
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
+ currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {}
+ defaults = defaults.merge(currency)
precision = options[:precision] || defaults[:precision]
unit = options[:unit] || defaults[:unit]
@@ -109,9 +109,9 @@ module ActionView
def number_to_percentage(number, options = {})
options.symbolize_keys!
- defaults, percentage = I18n.translate([:'number.format', :'number.percentage.format'],
- :locale => options[:locale]) || [{},{}]
- defaults = defaults.merge(percentage)
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
+ percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {}
+ defaults = defaults.merge(percentage)
precision = options[:precision] || defaults[:precision]
separator = options[:separator] || defaults[:separator]
@@ -151,7 +151,7 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
- defaults = I18n.translate(:'number.format', :locale => options[:locale]) || {}
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' +
@@ -195,9 +195,10 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
- defaults, precision_defaults = I18n.translate([:'number.format', :'number.precision.format'],
- :locale => options[:locale]) || [{},{}]
- defaults = defaults.merge(precision_defaults)
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
+ precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale],
+ :raise => true) rescue {}
+ defaults = defaults.merge(precision_defaults)
unless args.empty?
ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' +
@@ -209,12 +210,14 @@ module ActionView
separator ||= (options[:separator] || defaults[:separator])
delimiter ||= (options[:delimiter] || defaults[:delimiter])
- rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
- number_with_delimiter("%01.#{precision}f" % rounded_number,
- :separator => separator,
- :delimiter => delimiter)
- rescue
- number
+ begin
+ rounded_number = (Float(number) * (10 ** precision)).round.to_f / 10 ** precision
+ number_with_delimiter("%01.#{precision}f" % rounded_number,
+ :separator => separator,
+ :delimiter => delimiter)
+ rescue
+ number
+ end
end
STORAGE_UNITS = %w( Bytes KB MB GB TB ).freeze
@@ -251,8 +254,8 @@ module ActionView
options = args.extract_options!
options.symbolize_keys!
- defaults, human = I18n.translate([:'number.format', :'number.human.format'],
- :locale => options[:locale]) || [{},{}]
+ defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {}
+ human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {}
defaults = defaults.merge(human)
unless args.empty?
@@ -272,13 +275,16 @@ module ActionView
number /= 1024 ** exponent
unit = STORAGE_UNITS[exponent]
- number_with_precision(number,
- :precision => precision,
- :separator => separator,
- :delimiter => delimiter
- ).sub(/(\d)(#{Regexp.escape(separator)}[1-9]*)?0+\z/, '\1') + " #{unit}"
- rescue
- number
+ begin
+ escaped_separator = Regexp.escape(separator)
+ number_with_precision(number,
+ :precision => precision,
+ :separator => separator,
+ :delimiter => delimiter
+ ).sub(/(\d)(#{escaped_separator}[1-9]*)?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') + " #{unit}"
+ rescue
+ number
+ 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 022edf23c8..f9096d0029 100644
--- a/actionpack/lib/action_view/helpers/text_helper.rb
+++ b/actionpack/lib/action_view/helpers/text_helper.rb
@@ -558,7 +558,7 @@ module ActionView
[-\w]+ # subdomain or domain
(?:\.[-\w]+)* # remaining subdomains or domain
(?::\d+)? # port
- (?:/(?:(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$])))*)* # path
+ (?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path
(?:\?[\w\+@%&=.;-]+)? # query string
(?:\#[\w\-]*)? # trailing anchor
)
diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb
index f31502d99d..7ba42a3b72 100644
--- a/actionpack/lib/action_view/helpers/url_helper.rb
+++ b/actionpack/lib/action_view/helpers/url_helper.rb
@@ -442,7 +442,7 @@ module ActionView
# # => <a href="mailto:me@domain.com">me@domain.com</a>
#
# mail_to "me@domain.com", "My email", :encode => "javascript"
- # # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script>
+ # # => <script type="text/javascript">eval(decodeURIComponent('%64%6f%63...%27%29%3b'))</script>
#
# mail_to "me@domain.com", "My email", :encode => "hex"
# # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a>
@@ -476,7 +476,7 @@ module ActionView
"document.write('#{content_tag("a", name || email_address_obfuscated, html_options.merge({ "href" => "mailto:"+email_address+extras }))}');".each_byte do |c|
string << sprintf("%%%x", c)
end
- "<script type=\"#{Mime::JS}\">eval(unescape('#{string}'))</script>"
+ "<script type=\"#{Mime::JS}\">eval(decodeURIComponent('#{string}'))</script>"
elsif encode == "hex"
email_address_encoded = ''
email_address_obfuscated.each_byte do |c|
diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb
index eb74d4a4c7..894b88534c 100644
--- a/actionpack/lib/action_view/partials.rb
+++ b/actionpack/lib/action_view/partials.rb
@@ -146,7 +146,7 @@ module ActionView
def find_partial_path(partial_path)
if partial_path.include?('/')
- "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}"
+ File.join(File.dirname(partial_path), "_#{File.basename(partial_path)}")
elsif respond_to?(:controller)
"#{controller.class.controller_path}/_#{partial_path}"
else
diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb
index 5fe1ca86f3..89ac500717 100644
--- a/actionpack/lib/action_view/renderable.rb
+++ b/actionpack/lib/action_view/renderable.rb
@@ -31,10 +31,10 @@ module ActionView
view.send(:evaluate_assigns)
view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type)
- view.send(:execute, method(local_assigns), local_assigns)
+ view.send(:execute, method_name(local_assigns), local_assigns)
end
- def method(local_assigns)
+ def method_name(local_assigns)
if local_assigns && local_assigns.any?
local_assigns_keys = "locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}"
end
@@ -44,7 +44,7 @@ module ActionView
private
# Compile and evaluate the template's code (if necessary)
def compile(local_assigns)
- render_symbol = method(local_assigns)
+ render_symbol = method_name(local_assigns)
@@mutex.synchronize do
if recompile?(render_symbol)
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index b281ff61f2..5dc6708431 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -22,6 +22,14 @@ module ActionView #:nodoc:
end
memoize :format_and_extension
+ def multipart?
+ format && format.include?('.')
+ end
+
+ def content_type
+ format.gsub('.', '/')
+ end
+
def mime_type
Mime::Type.lookup_by_extension(format) if format
end
@@ -84,7 +92,7 @@ module ActionView #:nodoc:
# [base_path, name, format, extension]
def split(file)
if m = file.match(/^(.*\/)?([^\.]+)\.?(\w+)?\.?(\w+)?\.?(\w+)?$/)
- if m[5] # Mulipart formats
+ if m[5] # Multipart formats
[m[1], m[2], "#{m[3]}.#{m[4]}", m[5]]
elsif m[4] # Single format
[m[1], m[2], m[3], m[4]]