aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/MIT-LICENSE2
-rw-r--r--actionpack/lib/action_controller.rb5
-rw-r--r--actionpack/lib/action_controller/base.rb5
-rw-r--r--actionpack/lib/action_controller/lock.rb16
-rw-r--r--actionpack/lib/action_controller/middlewares.rb3
-rw-r--r--actionpack/lib/action_controller/params_parser.rb71
-rw-r--r--actionpack/lib/action_controller/rack_ext.rb25
-rw-r--r--actionpack/lib/action_controller/rack_ext/lock.rb21
-rw-r--r--actionpack/lib/action_controller/rack_ext/multipart.rb22
-rw-r--r--actionpack/lib/action_controller/rack_ext/parse_query.rb18
-rwxr-xr-xactionpack/lib/action_controller/request.rb34
-rw-r--r--actionpack/lib/action_controller/request_parser.rb315
-rw-r--r--actionpack/lib/action_controller/request_profiler.rb168
-rw-r--r--actionpack/lib/action_controller/rewindable_input.rb30
-rw-r--r--actionpack/lib/action_controller/uploaded_file.rb7
-rw-r--r--actionpack/lib/action_controller/url_encoded_pair_parser.rb61
-rw-r--r--actionpack/lib/action_pack.rb2
-rw-r--r--actionpack/lib/action_view.rb2
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb106
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/template.rb2
-rw-r--r--actionpack/lib/action_view/template_handlers.rb6
-rw-r--r--actionpack/test/controller/rack_test.rb2
-rw-r--r--actionpack/test/controller/request/multipart_params_parsing_test.rb6
-rw-r--r--actionpack/test/controller/request/url_encoded_params_parsing_test.rb12
-rw-r--r--actionpack/test/template/date_helper_test.rb95
26 files changed, 412 insertions, 634 deletions
diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE
index 13c90d46e9..e7accc5ea1 100644
--- a/actionpack/MIT-LICENSE
+++ b/actionpack/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2008 David Heinemeier Hansson
+Copyright (c) 2004-2009 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index f808bdd910..dca07a0afc 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -56,9 +56,9 @@ module ActionController
autoload :Integration, 'action_controller/integration'
autoload :IntegrationTest, 'action_controller/integration'
autoload :Layout, 'action_controller/layout'
- autoload :Lock, 'action_controller/lock'
autoload :MiddlewareStack, 'action_controller/middleware_stack'
autoload :MimeResponds, 'action_controller/mime_responds'
+ autoload :ParamsParser, 'action_controller/params_parser'
autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes'
autoload :RecordIdentifier, 'action_controller/record_identifier'
autoload :Request, 'action_controller/request'
@@ -75,6 +75,7 @@ module ActionController
autoload :TestCase, 'action_controller/test_case'
autoload :TestProcess, 'action_controller/test_process'
autoload :Translation, 'action_controller/translation'
+ autoload :UploadedFile, 'action_controller/uploaded_file'
autoload :UploadedStringIO, 'action_controller/uploaded_file'
autoload :UploadedTempfile, 'action_controller/uploaded_file'
autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser'
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index e22114195c..7a380bd1fb 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -301,10 +301,7 @@ module ActionController #:nodoc:
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
- @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
- Mime::URL_ENCODED_FORM => :url_encoded_form,
- Mime::XML => :xml_simple,
- Mime::JSON => :json }
+ @@param_parsers = {}
cattr_accessor :param_parsers
# Controls the default charset for all renders.
diff --git a/actionpack/lib/action_controller/lock.rb b/actionpack/lib/action_controller/lock.rb
deleted file mode 100644
index c50762216e..0000000000
--- a/actionpack/lib/action_controller/lock.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-module ActionController
- class Lock
- FLAG = 'rack.multithread'.freeze
-
- def initialize(app, lock = Mutex.new)
- @app, @lock = app, lock
- end
-
- def call(env)
- old, env[FLAG] = env[FLAG], false
- @lock.synchronize { @app.call(env) }
- ensure
- env[FLAG] = old
- end
- end
-end
diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb
index 0f038b8856..30dbc26f11 100644
--- a/actionpack/lib/action_controller/middlewares.rb
+++ b/actionpack/lib/action_controller/middlewares.rb
@@ -1,4 +1,4 @@
-use "ActionController::Lock", :if => lambda {
+use "Rack::Lock", :if => lambda {
!ActionController::Base.allow_concurrency
}
@@ -19,4 +19,5 @@ use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) }
end
use ActionController::RewindableInput
+use ActionController::ParamsParser
use Rack::MethodOverride
diff --git a/actionpack/lib/action_controller/params_parser.rb b/actionpack/lib/action_controller/params_parser.rb
new file mode 100644
index 0000000000..d269fe07fa
--- /dev/null
+++ b/actionpack/lib/action_controller/params_parser.rb
@@ -0,0 +1,71 @@
+module ActionController
+ class ParamsParser
+ ActionController::Base.param_parsers[Mime::XML] = :xml_simple
+ ActionController::Base.param_parsers[Mime::JSON] = :json
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ if params = parse_formatted_parameters(env)
+ env["action_controller.request.request_parameters"] = params
+ end
+
+ @app.call(env)
+ end
+
+ private
+ def parse_formatted_parameters(env)
+ request = Request.new(env)
+
+ return false if request.content_length.zero?
+
+ mime_type = content_type_from_legacy_post_data_format_header(env) || request.content_type
+ strategy = ActionController::Base.param_parsers[mime_type]
+
+ return false unless strategy
+
+ case strategy
+ when Proc
+ strategy.call(request.raw_post)
+ when :xml_simple, :xml_node
+ body = request.raw_post
+ body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
+ when :yaml
+ YAML.load(request.raw_post)
+ when :json
+ body = request.raw_post
+ if body.blank?
+ {}
+ else
+ data = ActiveSupport::JSON.decode(body)
+ data = {:_json => data} unless data.is_a?(Hash)
+ data.with_indifferent_access
+ end
+ else
+ false
+ end
+ rescue Exception => e # YAML, XML or Ruby code block errors
+ raise
+ { "body" => request.raw_post,
+ "content_type" => request.content_type,
+ "content_length" => request.content_length,
+ "exception" => "#{e.message} (#{e.class})",
+ "backtrace" => e.backtrace }
+ end
+
+ def content_type_from_legacy_post_data_format_header(env)
+ if x_post_format = env['HTTP_X_POST_DATA_FORMAT']
+ case x_post_format.to_s.downcase
+ when 'yaml'
+ return Mime::YAML
+ when 'xml'
+ return Mime::XML
+ end
+ end
+
+ nil
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext.rb b/actionpack/lib/action_controller/rack_ext.rb
index 3b142307e9..2ba6654e3d 100644
--- a/actionpack/lib/action_controller/rack_ext.rb
+++ b/actionpack/lib/action_controller/rack_ext.rb
@@ -1,22 +1,3 @@
-module Rack
- module Utils
- module Multipart
- class << self
- def parse_multipart_with_rewind(env)
- result = parse_multipart_without_rewind(env)
-
- begin
- env['rack.input'].rewind if env['rack.input'].respond_to?(:rewind)
- rescue Errno::ESPIPE
- # Handles exceptions raised by input streams that cannot be rewound
- # such as when using plain CGI under Apache
- end
-
- result
- end
-
- alias_method_chain :parse_multipart, :rewind
- end
- end
- end
-end
+require 'action_controller/rack_ext/lock'
+require 'action_controller/rack_ext/multipart'
+require 'action_controller/rack_ext/parse_query'
diff --git a/actionpack/lib/action_controller/rack_ext/lock.rb b/actionpack/lib/action_controller/rack_ext/lock.rb
new file mode 100644
index 0000000000..9bf1889065
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/lock.rb
@@ -0,0 +1,21 @@
+module Rack
+ # Rack::Lock was commited to Rack core
+ # http://github.com/rack/rack/commit/7409b0c
+ # Remove this when Rack 1.0 is released
+ unless defined? Lock
+ class Lock
+ FLAG = 'rack.multithread'.freeze
+
+ def initialize(app, lock = Mutex.new)
+ @app, @lock = app, lock
+ end
+
+ def call(env)
+ old, env[FLAG] = env[FLAG], false
+ @lock.synchronize { @app.call(env) }
+ ensure
+ env[FLAG] = old
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/multipart.rb b/actionpack/lib/action_controller/rack_ext/multipart.rb
new file mode 100644
index 0000000000..3b142307e9
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/multipart.rb
@@ -0,0 +1,22 @@
+module Rack
+ module Utils
+ module Multipart
+ class << self
+ def parse_multipart_with_rewind(env)
+ result = parse_multipart_without_rewind(env)
+
+ begin
+ env['rack.input'].rewind if env['rack.input'].respond_to?(:rewind)
+ rescue Errno::ESPIPE
+ # Handles exceptions raised by input streams that cannot be rewound
+ # such as when using plain CGI under Apache
+ end
+
+ result
+ end
+
+ alias_method_chain :parse_multipart, :rewind
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/rack_ext/parse_query.rb b/actionpack/lib/action_controller/rack_ext/parse_query.rb
new file mode 100644
index 0000000000..2f21a57770
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_ext/parse_query.rb
@@ -0,0 +1,18 @@
+# Rack does not automatically cleanup Safari 2 AJAX POST body
+# This has not yet been commited to Rack, please +1 this ticket:
+# http://rack.lighthouseapp.com/projects/22435/tickets/19
+
+module Rack
+ module Utils
+ alias_method :parse_query_without_ajax_body_cleanup, :parse_query
+ module_function :parse_query_without_ajax_body_cleanup
+
+ def parse_query(qs, d = '&;')
+ qs = qs.dup
+ qs.chop! if qs[-1] == 0
+ qs.gsub!(/&_=$/, '')
+ parse_query_without_ajax_body_cleanup(qs, d)
+ end
+ module_function :parse_query
+ end
+end
diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb
index 3d13705f8f..09dcd684e8 100755
--- a/actionpack/lib/action_controller/request.rb
+++ b/actionpack/lib/action_controller/request.rb
@@ -9,11 +9,6 @@ module ActionController
class Request < Rack::Request
extend ActiveSupport::Memoizable
- def initialize(env)
- super
- @parser = ActionController::RequestParser.new(env)
- end
-
%w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
@@ -94,7 +89,11 @@ module ActionController
# For backward compatibility, the post \format is extracted from the
# X-Post-Data-Format HTTP header if present.
def content_type
- Mime::Type.lookup(@parser.content_type_without_parameters)
+ if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/
+ Mime::Type.lookup($1.strip.downcase)
+ else
+ nil
+ end
end
memoize :content_type
@@ -382,7 +381,11 @@ EOM
# Read the request \body. This is useful for web services that need to
# work with raw requests directly.
def raw_post
- @parser.raw_post
+ unless @env.include? 'RAW_POST_DATA'
+ @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i)
+ body.rewind if body.respond_to?(:rewind)
+ end
+ @env['RAW_POST_DATA']
end
# Returns both GET and POST \parameters in a single hash.
@@ -411,19 +414,30 @@ EOM
@env["rack.routing_args"] ||= {}
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
- @parser.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
+ @env['rack.input']
+ end
+ end
+
+ def form_data?
+ FORM_DATA_MEDIA_TYPES.include?(content_type.to_s)
end
# Override Rack's GET method to support nested query strings
def GET
- @parser.query_parameters
+ @env["action_controller.request.query_parameters"] ||= UrlEncodedPairParser.parse_query_parameters(query_string)
end
alias_method :query_parameters, :GET
# Override Rack's POST method to support nested query strings
def POST
- @parser.request_parameters
+ @env["action_controller.request.request_parameters"] ||= UrlEncodedPairParser.parse_hash_parameters(super)
end
alias_method :request_parameters, :POST
diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb
deleted file mode 100644
index d1739ef4d0..0000000000
--- a/actionpack/lib/action_controller/request_parser.rb
+++ /dev/null
@@ -1,315 +0,0 @@
-module ActionController
- class RequestParser
- def initialize(env)
- @env = env
- freeze
- end
-
- def request_parameters
- @env["action_controller.request_parser.request_parameters"] ||= parse_formatted_request_parameters
- end
-
- def query_parameters
- @env["action_controller.request_parser.query_parameters"] ||= self.class.parse_query_parameters(query_string)
- end
-
- # Returns the query string, accounting for server idiosyncrasies.
- def query_string
- @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '')
- 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
- @env['rack.input']
- end
- end
-
- # The raw content type string with its parameters stripped off.
- def content_type_without_parameters
- self.class.extract_content_type_without_parameters(content_type_with_parameters)
- end
-
- def raw_post
- unless @env.include? 'RAW_POST_DATA'
- @env['RAW_POST_DATA'] = body.read(content_length)
- body.rewind if body.respond_to?(:rewind)
- end
- @env['RAW_POST_DATA']
- end
-
- private
-
- def parse_formatted_request_parameters
- return {} if content_length.zero?
-
- content_type, boundary = self.class.extract_multipart_boundary(content_type_with_parameters)
-
- # Don't parse params for unknown requests.
- return {} if content_type.blank?
-
- mime_type = Mime::Type.lookup(content_type)
- strategy = ActionController::Base.param_parsers[mime_type]
-
- # Only multipart form parsing expects a stream.
- body = (strategy && strategy != :multipart_form) ? raw_post : self.body
-
- case strategy
- when Proc
- strategy.call(body)
- when :url_encoded_form
- self.class.clean_up_ajax_request_body! body
- self.class.parse_query_parameters(body)
- when :multipart_form
- self.class.parse_multipart_form_parameters(body, boundary, content_length, @env)
- when :xml_simple, :xml_node
- body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
- when :yaml
- YAML.load(body)
- when :json
- if body.blank?
- {}
- else
- data = ActiveSupport::JSON.decode(body)
- data = {:_json => data} unless data.is_a?(Hash)
- data.with_indifferent_access
- end
- else
- {}
- end
- rescue Exception => e # YAML, XML or Ruby code block errors
- raise
- { "body" => body,
- "content_type" => content_type_with_parameters,
- "content_length" => content_length,
- "exception" => "#{e.message} (#{e.class})",
- "backtrace" => e.backtrace }
- end
-
- def content_length
- @env['CONTENT_LENGTH'].to_i
- end
-
- # The raw content type string. Use when you need parameters such as
- # charset or boundary which aren't included in the content_type MIME type.
- # Overridden by the X-POST_DATA_FORMAT header for backward compatibility.
- def content_type_with_parameters
- content_type_from_legacy_post_data_format_header || @env['CONTENT_TYPE'].to_s
- end
-
- def content_type_from_legacy_post_data_format_header
- if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
- case x_post_format.to_s.downcase
- when 'yaml'; 'application/x-yaml'
- when 'xml'; 'application/xml'
- end
- end
- end
-
- class << self
- def parse_query_parameters(query_string)
- return {} if query_string.blank?
-
- pairs = query_string.split('&').collect do |chunk|
- next if chunk.empty?
- key, value = chunk.split('=', 2)
- next if key.empty?
- value = value.nil? ? nil : CGI.unescape(value)
- [ CGI.unescape(key), value ]
- end.compact
-
- UrlEncodedPairParser.new(pairs).result
- end
-
- def parse_request_parameters(params)
- parser = UrlEncodedPairParser.new
-
- params = params.dup
- until params.empty?
- for key, value in params
- if key.blank?
- params.delete key
- elsif !key.include?('[')
- # much faster to test for the most common case first (GET)
- # and avoid the call to build_deep_hash
- parser.result[key] = get_typed_value(value[0])
- params.delete key
- elsif value.is_a?(Array)
- parser.parse(key, get_typed_value(value.shift))
- params.delete key if value.empty?
- else
- raise TypeError, "Expected array, found #{value.inspect}"
- end
- end
- end
-
- parser.result
- end
-
- def parse_multipart_form_parameters(body, boundary, body_size, env)
- parse_request_parameters(read_multipart(body, boundary, body_size, env))
- end
-
- def extract_multipart_boundary(content_type_with_parameters)
- if content_type_with_parameters =~ MULTIPART_BOUNDARY
- ['multipart/form-data', $1.dup]
- else
- extract_content_type_without_parameters(content_type_with_parameters)
- end
- end
-
- def extract_content_type_without_parameters(content_type_with_parameters)
- $1.strip.downcase if content_type_with_parameters =~ /^([^,\;]*)/
- end
-
- def clean_up_ajax_request_body!(body)
- body.chop! if body[-1] == 0
- body.gsub!(/&_=$/, '')
- end
-
-
- private
- def get_typed_value(value)
- case value
- when String
- value
- when NilClass
- ''
- when Array
- value.map { |v| get_typed_value(v) }
- else
- if value.respond_to? :original_filename
- # Uploaded file
- if value.original_filename
- value
- # Multipart param
- else
- result = value.read
- value.rewind
- result
- end
- # Unknown value, neither string nor multipart.
- else
- raise "Unknown form value: #{value.inspect}"
- end
- end
- end
-
- MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
-
- EOL = "\015\012"
-
- def read_multipart(body, boundary, body_size, env)
- params = Hash.new([])
- boundary = "--" + boundary
- quoted_boundary = Regexp.quote(boundary)
- buf = ""
- bufsize = 10 * 1024
- boundary_end=""
-
- # start multipart/form-data
- body.binmode if defined? body.binmode
- case body
- when File
- body.set_encoding(Encoding::BINARY) if body.respond_to?(:set_encoding)
- when StringIO
- body.string.force_encoding(Encoding::BINARY) if body.string.respond_to?(:force_encoding)
- end
- boundary_size = boundary.size + EOL.size
- body_size -= boundary_size
- status = body.read(boundary_size)
- if nil == status
- raise EOFError, "no content body"
- elsif boundary + EOL != status
- raise EOFError, "bad content body"
- end
-
- loop do
- head = nil
- content =
- if 10240 < body_size
- UploadedTempfile.new("CGI")
- else
- UploadedStringIO.new
- end
- content.binmode if defined? content.binmode
-
- until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
-
- if (not head) and /#{EOL}#{EOL}/n.match(buf)
- buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
- head = $1.dup
- ""
- end
- next
- end
-
- if head and ( (EOL + boundary + EOL).size < buf.size )
- content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
- buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
- end
-
- c = if bufsize < body_size
- body.read(bufsize)
- else
- body.read(body_size)
- end
- if c.nil? || c.empty?
- raise EOFError, "bad content body"
- end
- buf.concat(c)
- body_size -= c.size
- end
-
- buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
- content.print $1
- if "--" == $2
- body_size = -1
- end
- boundary_end = $2.dup
- ""
- end
-
- content.rewind
-
- head =~ /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni
- if filename = $1 || $2
- if /Mac/ni.match(env['HTTP_USER_AGENT']) and
- /Mozilla/ni.match(env['HTTP_USER_AGENT']) and
- (not /MSIE/ni.match(env['HTTP_USER_AGENT']))
- filename = CGI.unescape(filename)
- end
- content.original_path = filename.dup
- end
-
- head =~ /Content-Type: ([^\r]*)/ni
- content.content_type = $1.dup if $1
-
- head =~ /Content-Disposition:.* name="?([^\";]*)"?/ni
- name = $1.dup if $1
-
- if params.has_key?(name)
- params[name].push(content)
- else
- params[name] = [content]
- end
- break if body_size == -1
- end
- raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
-
- begin
- body.rewind if body.respond_to?(:rewind)
- rescue Errno::ESPIPE
- # Handles exceptions raised by input streams that cannot be rewound
- # such as when using plain CGI under Apache
- end
-
- params
- end
- end # class << self
- end
-end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
deleted file mode 100644
index 80cd55334f..0000000000
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ /dev/null
@@ -1,168 +0,0 @@
-require 'optparse'
-
-module ActionController
- class RequestProfiler
- # Wrap up the integration session runner.
- class Sandbox
- include Integration::Runner
-
- def self.benchmark(n, script)
- new(script).benchmark(n)
- end
-
- def initialize(script_path)
- @quiet = false
- define_run_method(script_path)
- reset!
- end
-
- def benchmark(n, profiling = false)
- @quiet = true
- print ' '
-
- ms = Benchmark.ms do
- n.times do |i|
- run(profiling)
- print_progress(i)
- end
- end
-
- puts
- ms
- ensure
- @quiet = false
- end
-
- def say(message)
- puts " #{message}" unless @quiet
- end
-
- private
- def define_run_method(script_path)
- script = File.read(script_path)
-
- source = <<-end_source
- def run(profiling = false)
- if profiling
- RubyProf.resume do
- #{script}
- end
- else
- #{script}
- end
-
- old_request_count = request_count
- reset!
- self.request_count = old_request_count
- end
- end_source
-
- instance_eval source, script_path, 1
- end
-
- def print_progress(i)
- print "\n " if i % 60 == 0
- print ' ' if i % 10 == 0
- print '.'
- $stdout.flush
- end
- end
-
-
- attr_reader :options
-
- def initialize(options = {})
- @options = default_options.merge(options)
- end
-
-
- def self.run(args = nil, options = {})
- profiler = new(options)
- profiler.parse_options(args) if args
- profiler.run
- end
-
- def run
- sandbox = Sandbox.new(options[:script])
-
- puts 'Warming up once'
-
- elapsed = warmup(sandbox)
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, sandbox.request_count, 1000 * sandbox.request_count / elapsed]
- puts "\n#{options[:benchmark] ? 'Benchmarking' : 'Profiling'} #{options[:n]}x"
-
- options[:benchmark] ? benchmark(sandbox) : profile(sandbox)
- end
-
- def profile(sandbox)
- load_ruby_prof
-
- benchmark(sandbox, true)
- results = RubyProf.stop
-
- show_profile_results results
- results
- end
-
- def benchmark(sandbox, profiling = false)
- sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n], profiling)
- count = sandbox.request_count.to_i
- puts '%.0f ms, %d requests, %d req/sec' % [elapsed, count, 1000 * count / elapsed]
- end
-
- def warmup(sandbox)
- Benchmark.ms { sandbox.run(false) }
- end
-
- def default_options
- { :n => 100, :open => 'open %s &' }
- end
-
- # Parse command-line options
- def parse_options(args)
- OptionParser.new do |opt|
- opt.banner = "USAGE: #{$0} [options] [session script path]"
-
- opt.on('-n', '--times [100]', 'How many requests to process. Defaults to 100.') { |v| options[:n] = v.to_i if v }
- opt.on('-b', '--benchmark', 'Benchmark instead of profiling') { |v| options[:benchmark] = v }
- opt.on('-m', '--measure [mode]', 'Which ruby-prof measure mode to use: process_time, wall_time, cpu_time, allocations, or memory. Defaults to process_time.') { |v| options[:measure] = v }
- opt.on('--open [CMD]', 'Command to open profile results. Defaults to "open %s &"') { |v| options[:open] = v }
- opt.on('-h', '--help', 'Show this help') { puts opt; exit }
-
- opt.parse args
-
- if args.empty?
- puts opt
- exit
- end
- options[:script] = args.pop
- end
- end
-
- protected
- def load_ruby_prof
- begin
- gem 'ruby-prof', '>= 0.6.1'
- require 'ruby-prof'
- if mode = options[:measure]
- RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
- end
- rescue LoadError
- abort '`gem install ruby-prof` to use the profiler'
- end
- end
-
- def show_profile_results(results)
- File.open "#{RAILS_ROOT}/tmp/profile-graph.html", 'w' do |file|
- RubyProf::GraphHtmlPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
-
- File.open "#{RAILS_ROOT}/tmp/profile-flat.txt", 'w' do |file|
- RubyProf::FlatPrinter.new(results).print(file)
- `#{options[:open] % file.path}` if options[:open]
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/rewindable_input.rb b/actionpack/lib/action_controller/rewindable_input.rb
index 058453ea68..36f655c51e 100644
--- a/actionpack/lib/action_controller/rewindable_input.rb
+++ b/actionpack/lib/action_controller/rewindable_input.rb
@@ -3,33 +3,17 @@ module ActionController
class RewindableIO < ActiveSupport::BasicObject
def initialize(io)
@io = io
- end
-
- def read(*args)
- read_original_io
- @io.read(*args)
- end
-
- def rewind
- read_original_io
- @io.rewind
- end
-
- def string
- @string
+ @rewindable = io.is_a?(StringIO)
end
def method_missing(method, *args, &block)
- @io.send(method, *args, &block)
- end
-
- private
- def read_original_io
- unless @string
- @string = @io.read
- @io = StringIO.new(@string)
- end
+ unless @rewindable
+ @io = StringIO.new(@io.read)
+ @rewindable = true
end
+
+ @io.__send__(method, *args, &block)
+ end
end
def initialize(app)
diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb
index ea4845c68f..376ba3621a 100644
--- a/actionpack/lib/action_controller/uploaded_file.rb
+++ b/actionpack/lib/action_controller/uploaded_file.rb
@@ -7,6 +7,13 @@ module ActionController
end
end
+ def self.extended(object)
+ object.class_eval do
+ attr_accessor :original_path, :content_type
+ alias_method :local_path, :path
+ end
+ end
+
# Take the basename of the upload's original filename.
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
index 9883ad0d85..b63dca987d 100644
--- a/actionpack/lib/action_controller/url_encoded_pair_parser.rb
+++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb
@@ -1,5 +1,66 @@
module ActionController
class UrlEncodedPairParser < StringScanner #:nodoc:
+ class << self
+ def parse_query_parameters(query_string)
+ return {} if query_string.blank?
+
+ pairs = query_string.split('&').collect do |chunk|
+ next if chunk.empty?
+ key, value = chunk.split('=', 2)
+ next if key.empty?
+ value = value.nil? ? nil : CGI.unescape(value)
+ [ CGI.unescape(key), value ]
+ end.compact
+
+ new(pairs).result
+ end
+
+ def parse_hash_parameters(params)
+ parser = new
+
+ params = params.dup
+ until params.empty?
+ for key, value in params
+ if key.blank?
+ params.delete(key)
+ elsif value.is_a?(Array)
+ parser.parse(key, get_typed_value(value.shift))
+ params.delete(key) if value.empty?
+ else
+ parser.parse(key, get_typed_value(value))
+ params.delete(key)
+ end
+ end
+ end
+
+ parser.result
+ end
+
+ private
+ def get_typed_value(value)
+ case value
+ when String
+ value
+ when NilClass
+ ''
+ when Array
+ value.map { |v| get_typed_value(v) }
+ when Hash
+ if value.has_key?(:tempfile)
+ upload = value[:tempfile]
+ upload.extend(UploadedFile)
+ upload.original_path = value[:filename]
+ upload.content_type = value[:type]
+ upload
+ else
+ nil
+ end
+ else
+ raise "Unknown form value: #{value.inspect}"
+ end
+ end
+ end
+
attr_reader :top, :parent, :result
def initialize(pairs = [])
diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb
index c7fd3092e7..b90f89be39 100644
--- a/actionpack/lib/action_pack.rb
+++ b/actionpack/lib/action_pack.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb
index 210a5f1a93..0b710bd8d9 100644
--- a/actionpack/lib/action_view.rb
+++ b/actionpack/lib/action_view.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2008 David Heinemeier Hansson
+# Copyright (c) 2004-2009 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
index c6561f43a0..f6abea38ed 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -6,13 +6,19 @@ module ActionView
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
- # the assets exist before linking to them.
+ # the assets exist before linking to them:
+ #
+ # image_tag("rails.png")
+ # # => <img alt="Rails src="/images/rails.png?1230601161" />
+ # stylesheet_link_tag("application")
+ # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
# === Using asset hosts
#
# By default, Rails links to these assets on the current host in the public
- # folder, but you can direct Rails to link to assets from a dedicated asset server by
- # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>.
+ # folder, but you can direct Rails to link to assets from a dedicated asset
+ # server by setting ActionController::Base.asset_host in the application
+ # configuration, typically in <tt>config/environments/production.rb</tt>.
# For example, you'd define <tt>assets.example.com</tt> to be your asset
# host this way:
#
@@ -21,45 +27,49 @@ module ActionView
# Helpers take that into account:
#
# image_tag("rails.png")
- # => <img src="http://assets.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # Browsers typically open at most two simultaneous connections to a single host,
- # which means your assets often have to wait for other assets to finish downloading.
- # You can alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt>
- # (for example, "assets%d.example.com") to automatically distribute asset requests
- # among four hosts (e.g., "assets0.example.com" through "assets3.example.com") so
- # browsers will open eight simultaneous connections rather than two.
+ # Browsers typically open at most two simultaneous connections to a single
+ # host, which means your assets often have to wait for other assets to finish
+ # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the
+ # +asset_host+. For example, "assets%d.example.com". If that wildcard is
+ # present Rails distributes asset requests among the corresponding four hosts
+ # "assets0.example.com", ..., "assets3.example.com". With this trick browsers
+ # will open eight simultaneous connections rather than two.
#
# image_tag("rails.png")
- # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
- # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
- # your ISP.
+ # To do this, you can either setup four actual hosts, or you can use wildcard
+ # DNS to CNAME the wildcard to a single asset host. You can read more about
+ # setting up your DNS CNAME records from your ISP.
#
# Note: This is purely a browser performance optimization and is not meant
# for server load balancing. See http://www.die.net/musings/page_load_time/
# for background.
#
- # Alternatively, you can exert more control over the asset host by setting <tt>asset_host</tt> to a proc
- # that takes a single source argument. This is useful if you are unable to setup 4 actual hosts or have
- # fewer/more than 4 hosts. The example proc below generates http://assets1.example.com and
- # http://assets2.example.com randomly.
+ # Alternatively, you can exert more control over the asset host by setting
+ # +asset_host+ to a proc like this:
#
- # ActionController::Base.asset_host = Proc.new { |source| "http://assets#{rand(2) + 1}.example.com" }
+ # ActionController::Base.asset_host = Proc.new { |source|
+ # "http://assets#{rand(2) + 1}.example.com"
+ # }
# image_tag("rails.png")
- # => <img src="http://assets2.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets1.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
+ #
+ # The example above generates "http://assets1.example.com" and
+ # "http://assets2.example.com" randomly. This option is useful for example if
+ # you need fewer/more than four hosts, custom host names, etc.
#
- # The proc takes a <tt>source</tt> parameter (which is the path of the source asset) and an optional
- # <tt>request</tt> parameter (which is an entire instance of an <tt>ActionController::AbstractRequest</tt>
- # subclass). This can be used to generate a particular asset host depending on the asset path and the particular
- # request.
+ # As you see the proc takes a +source+ parameter. That's a string with the
+ # absolute path of the asset with any extensions and timestamps in place,
+ # for example "/images/rails.png?1230601161".
#
# ActionController::Base.asset_host = Proc.new { |source|
# if source.starts_with?('/images')
@@ -69,14 +79,16 @@ module ActionView
# end
# }
# image_tag("rails.png")
- # => <img src="http://images.example.com/images/rails.png" alt="Rails" />
+ # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" />
# stylesheet_link_tag("application")
- # => <link href="http://assets.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
+ # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" />
#
- # The optional <tt>request</tt> parameter to the proc is useful in particular for serving assets from an
- # SSL-protected page. The example proc below disables asset hosting for HTTPS connections, while still sending
- # assets for plain HTTP requests from asset hosts. This is useful for avoiding mixed media warnings when serving
- # non-HTTP assets from HTTPS web pages when you don't have an SSL certificate for each of the asset hosts.
+ # Alternatively you may ask for a second parameter +request+. That one is
+ # particularly useful for serving assets from an SSL-protected page. The
+ # example proc below disables asset hosting for HTTPS connections, while
+ # still sending assets for plain HTTP requests from asset hosts. If you don't
+ # have SSL certificates for each of the asset hosts this technique allows you
+ # to avoid warnings in the client about mixed media.
#
# ActionController::Base.asset_host = Proc.new { |source, request|
# if request.ssl?
@@ -86,7 +98,8 @@ module ActionView
# end
# }
#
- # You can also implement a custom asset host object that responds to the call method and tasks one or two parameters just like the proc.
+ # You can also implement a custom asset host object that responds to +call+
+ # and takes either one or two parameters just like the proc.
#
# config.action_controller.asset_host = AssetHostingWithMinimumSsl.new(
# "http://asset%d.example.com", "https://asset1.example.com"
@@ -94,12 +107,15 @@ module ActionView
#
# === Using asset timestamps
#
- # By default, Rails will append all asset paths with that asset's timestamp. This allows you to set a cache-expiration date for the
- # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
- # which then updates the URL as the timestamp is part of that, which in turn busts the cache).
+ # By default, Rails appends asset's timestamps to all asset paths. This allows
+ # you to set a cache-expiration date for the asset far into the future, but
+ # still be able to instantly invalidate it by simply updating the file (and
+ # hence updating the timestamp, which then updates the URL as the timestamp
+ # is part of that, which in turn busts the cache).
#
- # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
- # advantage of this feature. Here's an example for Apache:
+ # It's the responsibility of the web server you use to set the far-future
+ # expiration date on cache assets that you need to take advantage of this
+ # feature. Here's an example for Apache:
#
# # Asset Expiration
# ExpiresActive On
@@ -107,11 +123,13 @@ module ActionView
# ExpiresDefault "access plus 1 year"
# </FilesMatch>
#
- # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
- # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
- # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
- # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
- # requested over and over).
+ # Also note that in order for this to work, all your application servers must
+ # return the same timestamps. This means that they must have their clocks
+ # synchronized. If one of them drifts out of sync, you'll see different
+ # timestamps at random and the cache won't work. In that case the browser
+ # will request the same assets over and over again even thought they didn't
+ # change. You can use something like Live HTTP Headers for Firefox to verify
+ # that the cache is indeed working.
module AssetTagHelper
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb
index 4305617ac8..b4c1adbe76 100644
--- a/actionpack/lib/action_view/helpers/date_helper.rb
+++ b/actionpack/lib/action_view/helpers/date_helper.rb
@@ -860,7 +860,7 @@ module ActionView
# => post[written_on(1i)]
def input_name_from_type(type)
prefix = @options[:prefix] || ActionView::Helpers::DateTimeSelector::DEFAULT_PREFIX
- prefix += "[#{@options[:index]}]" if @options[:index]
+ prefix += "[#{@options[:index]}]" if @options.has_key?(:index)
field_name = @options[:field_name] || type
if @options[:include_position]
@@ -923,7 +923,7 @@ module ActionView
options[:field_name] = @method_name
options[:include_position] = true
options[:prefix] ||= @object_name
- options[:index] ||= @auto_index
+ options[:index] = @auto_index if @auto_index && !options.has_key?(:index)
options[:datetime_separator] ||= ' &mdash; '
options[:time_separator] ||= ' : '
@@ -961,15 +961,15 @@ module ActionView
class FormBuilder
def date_select(method, options = {}, html_options = {})
- @template.date_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.date_select(@object_name, method, objectify_options(options), html_options)
end
def time_select(method, options = {}, html_options = {})
- @template.time_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.time_select(@object_name, method, objectify_options(options), html_options)
end
def datetime_select(method, options = {}, html_options = {})
- @template.datetime_select(@object_name, method, options.merge(:object => @object), html_options)
+ @template.datetime_select(@object_name, method, objectify_options(options), html_options)
end
end
end
diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb
index 88ee07d95b..9d1e0d3ac5 100644
--- a/actionpack/lib/action_view/template.rb
+++ b/actionpack/lib/action_view/template.rb
@@ -204,7 +204,7 @@ module ActionView #:nodoc:
private
def valid_extension?(extension)
- Template.template_handler_extensions.include?(extension)
+ !Template.registered_template_handler(extension).nil?
end
def find_full_path(path, load_paths)
diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb
index d06ddd5fb5..205f8628f0 100644
--- a/actionpack/lib/action_view/template_handlers.rb
+++ b/actionpack/lib/action_view/template_handlers.rb
@@ -32,13 +32,17 @@ module ActionView #:nodoc:
@@template_handlers.keys.map(&:to_s).sort
end
+ def registered_template_handler(extension)
+ extension && @@template_handlers[extension.to_sym]
+ end
+
def register_default_template_handler(extension, klass)
register_template_handler(extension, klass)
@@default_template_handlers = klass
end
def handler_class_for_extension(extension)
- (extension && @@template_handlers[extension.to_sym]) || @@default_template_handlers
+ registered_template_handler(extension) || @@default_template_handlers
end
end
end
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
index 8fd004a9e9..8ad42614b4 100644
--- a/actionpack/test/controller/rack_test.rb
+++ b/actionpack/test/controller/rack_test.rb
@@ -57,7 +57,7 @@ class BaseRackTest < Test::Unit::TestCase
@request.env['REQUEST_METHOD'] = 'POST'
@request.env['CONTENT_LENGTH'] = data.length
@request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
- @request.env['RAW_POST_DATA'] = data
+ @request.env['rack.input'] = StringIO.new(data)
end
end
diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb
index 137fdbee54..d7ade40f71 100644
--- a/actionpack/test/controller/request/multipart_params_parsing_test.rb
+++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb
@@ -36,7 +36,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 'bar', params['foo']
file = params['file']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain", file.content_type
assert_equal 'contents', file.read
@@ -77,13 +77,13 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest
assert_equal 'bar', params['foo']
file = params['file']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'file.csv', file.original_filename
assert_nil file.content_type
assert_equal 'contents', file.read
file = params['flowers']
- assert_kind_of StringIO, file
+ assert_kind_of Tempfile, file
assert_equal 'flowers.jpg', file.original_filename
assert_equal "image/jpeg", file.content_type
assert_equal 19512, file.size
diff --git a/actionpack/test/controller/request/url_encoded_params_parsing_test.rb b/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
index ee2a239d50..89239687de 100644
--- a/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
+++ b/actionpack/test/controller/request/url_encoded_params_parsing_test.rb
@@ -150,6 +150,18 @@ class UrlEncodedParamsParsingTest < ActionController::IntegrationTest
assert_parses expected, query
end
+ test "parses params with Safari 2 trailing null character" do
+ query = "selected[]=1&selected[]=2&selected[]=3\0"
+ expected = { "selected" => [ "1", "2", "3" ] }
+ assert_parses expected, query
+ end
+
+ test "parses params with Prototype's hack around Safari 2 trailing null character" do
+ query = "selected[]=1&selected[]=2&selected[]=3&_="
+ expected = { "selected" => [ "1", "2", "3" ] }
+ assert_parses expected, query
+ end
+
test "passes through rack middleware and parses params" do
with_muck_middleware do
assert_parses({ "a" => { "b" => "c" } }, "a[b]=c")
diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb
index 6ec01b7a8f..92cdce2e45 100644
--- a/actionpack/test/template/date_helper_test.rb
+++ b/actionpack/test/template/date_helper_test.rb
@@ -1228,6 +1228,38 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal(expected, output_buffer)
end
+ def test_date_select_within_fields_for_with_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = 27
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.date_select(:written_on)
+ end
+
+ expected = "<select id='post_#{id}_written_on_1i' name='post[#{id}][written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_2i' name='post[#{id}][written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_3i' name='post[#{id}][written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+
+ assert_dom_equal(expected, output_buffer)
+ end
+
+ def test_date_select_within_fields_for_with_blank_index
+ @post = Post.new
+ @post.written_on = Date.new(2004, 6, 15)
+ id = nil
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.date_select(:written_on)
+ end
+
+ expected = "<select id='post_#{id}_written_on_1i' name='post[#{id}][written_on(1i)]'>\n<option value='1999'>1999</option>\n<option value='2000'>2000</option>\n<option value='2001'>2001</option>\n<option value='2002'>2002</option>\n<option value='2003'>2003</option>\n<option selected='selected' value='2004'>2004</option>\n<option value='2005'>2005</option>\n<option value='2006'>2006</option>\n<option value='2007'>2007</option>\n<option value='2008'>2008</option>\n<option value='2009'>2009</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_2i' name='post[#{id}][written_on(2i)]'>\n<option value='1'>January</option>\n<option value='2'>February</option>\n<option value='3'>March</option>\n<option value='4'>April</option>\n<option value='5'>May</option>\n<option selected='selected' value='6'>June</option>\n<option value='7'>July</option>\n<option value='8'>August</option>\n<option value='9'>September</option>\n<option value='10'>October</option>\n<option value='11'>November</option>\n<option value='12'>December</option>\n</select>\n"
+ expected << "<select id='post_#{id}_written_on_3i' name='post[#{id}][written_on(3i)]'>\n<option value='1'>1</option>\n<option value='2'>2</option>\n<option value='3'>3</option>\n<option value='4'>4</option>\n<option value='5'>5</option>\n<option value='6'>6</option>\n<option value='7'>7</option>\n<option value='8'>8</option>\n<option value='9'>9</option>\n<option value='10'>10</option>\n<option value='11'>11</option>\n<option value='12'>12</option>\n<option value='13'>13</option>\n<option value='14'>14</option>\n<option selected='selected' value='15'>15</option>\n<option value='16'>16</option>\n<option value='17'>17</option>\n<option value='18'>18</option>\n<option value='19'>19</option>\n<option value='20'>20</option>\n<option value='21'>21</option>\n<option value='22'>22</option>\n<option value='23'>23</option>\n<option value='24'>24</option>\n<option value='25'>25</option>\n<option value='26'>26</option>\n<option value='27'>27</option>\n<option value='28'>28</option>\n<option value='29'>29</option>\n<option value='30'>30</option>\n<option value='31'>31</option>\n</select>\n"
+
+ assert_dom_equal(expected, output_buffer)
+ end
+
def test_date_select_with_index
@post = Post.new
@post.written_on = Date.new(2004, 6, 15)
@@ -1243,7 +1275,6 @@ class DateHelperTest < ActionView::TestCase
expected << %{<select id="post_456_written_on_3i" name="post[#{id}][written_on(3i)]">\n}
expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
-
expected << "</select>\n"
assert_dom_equal expected, date_select("post", "written_on", :index => id)
@@ -1330,13 +1361,13 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, date_select("post", "written_on", :include_blank => true)
end
-
+
def test_date_select_with_nil_and_blank_and_order
@post = Post.new
start_year = Time.now.year-5
end_year = Time.now.year+5
-
+
expected = '<input name="post[written_on(3i)]" type="hidden" id="post_written_on_3i"/>' + "\n"
expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n}
expected << "<option value=\"\"></option>\n"
@@ -1966,6 +1997,40 @@ class DateHelperTest < ActionView::TestCase
assert_dom_equal expected, datetime_select("post", "updated_at", :index => id)
end
+ def test_datetime_select_within_fields_for_with_options_index
+ @post = Post.new
+ @post.updated_at = Time.local(2004, 6, 15, 16, 35)
+ id = 456
+
+ fields_for :post, @post, :index => id do |f|
+ concat f.datetime_select(:updated_at)
+ end
+
+ expected = %{<select id="post_456_updated_at_1i" name="post[#{id}][updated_at(1i)]">\n}
+ expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_2i" name="post[#{id}][updated_at(2i)]">\n}
+ expected << %{<option value="1">January</option>\n<option value="2">February</option>\n<option value="3">March</option>\n<option value="4">April</option>\n<option value="5">May</option>\n<option value="6" selected="selected">June</option>\n<option value="7">July</option>\n<option value="8">August</option>\n<option value="9">September</option>\n<option value="10">October</option>\n<option value="11">November</option>\n<option value="12">December</option>\n}
+ expected << "</select>\n"
+
+ expected << %{<select id="post_456_updated_at_3i" name="post[#{id}][updated_at(3i)]">\n}
+ expected << %{<option value="1">1</option>\n<option value="2">2</option>\n<option value="3">3</option>\n<option value="4">4</option>\n<option value="5">5</option>\n<option value="6">6</option>\n<option value="7">7</option>\n<option value="8">8</option>\n<option value="9">9</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15" selected="selected">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n}
+ expected << "</select>\n"
+
+ expected << " &mdash; "
+
+ expected << %{<select id="post_456_updated_at_4i" name="post[#{id}][updated_at(4i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16" selected="selected">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n}
+ expected << "</select>\n"
+ expected << " : "
+ expected << %{<select id="post_456_updated_at_5i" name="post[#{id}][updated_at(5i)]">\n}
+ expected << %{<option value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n<option value="24">24</option>\n<option value="25">25</option>\n<option value="26">26</option>\n<option value="27">27</option>\n<option value="28">28</option>\n<option value="29">29</option>\n<option value="30">30</option>\n<option value="31">31</option>\n<option value="32">32</option>\n<option value="33">33</option>\n<option value="34">34</option>\n<option value="35" selected="selected">35</option>\n<option value="36">36</option>\n<option value="37">37</option>\n<option value="38">38</option>\n<option value="39">39</option>\n<option value="40">40</option>\n<option value="41">41</option>\n<option value="42">42</option>\n<option value="43">43</option>\n<option value="44">44</option>\n<option value="45">45</option>\n<option value="46">46</option>\n<option value="47">47</option>\n<option value="48">48</option>\n<option value="49">49</option>\n<option value="50">50</option>\n<option value="51">51</option>\n<option value="52">52</option>\n<option value="53">53</option>\n<option value="54">54</option>\n<option value="55">55</option>\n<option value="56">56</option>\n<option value="57">57</option>\n<option value="58">58</option>\n<option value="59">59</option>\n}
+ expected << "</select>\n"
+
+ assert_dom_equal expected, output_buffer
+ end
+
def test_datetime_select_with_auto_index
@post = Post.new
@post.updated_at = Time.local(2004, 6, 15, 16, 35)
@@ -2253,7 +2318,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2265,7 +2330,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2279,7 +2344,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2291,7 +2356,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2305,7 +2370,7 @@ class DateHelperTest < ActionView::TestCase
@post = Post.new
@post.updated_at = Time.local(2008, 7, 16, 23, 30)
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2317,7 +2382,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2328,7 +2393,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_date_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2340,7 +2405,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2351,7 +2416,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_datetime_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2363,7 +2428,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2374,7 +2439,7 @@ class DateHelperTest < ActionView::TestCase
end
def test_select_time_should_not_change_passed_options_hash
- options = {
+ options = {
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,
@@ -2386,7 +2451,7 @@ class DateHelperTest < ActionView::TestCase
# note: the literal hash is intentional to show that the actual options hash isn't modified
# don't change this!
- assert_equal({
+ assert_equal({
:order => [ :year, :month, :day ],
:default => { :year => 2008, :month => 7, :day => 16, :hour => 23, :minute => 30, :second => 1 },
:discard_type => false,