diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2008-12-29 14:38:54 -0800 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2008-12-29 14:38:54 -0800 |
commit | 276ec16007b03d0a527fb0b83a7ee0b81e460fa1 (patch) | |
tree | 224491aa1948d613a551189028746d73400fccce /actionpack | |
parent | 2e053aec9bafa8735d70886f36dea06ea10dc4ce (diff) | |
parent | 490c26c8433a6d278bc61118782da360e8889646 (diff) | |
download | rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.gz rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.tar.bz2 rails-276ec16007b03d0a527fb0b83a7ee0b81e460fa1.zip |
Merge branch 'master' of git@github.com:rails/rails
Diffstat (limited to 'actionpack')
53 files changed, 2007 insertions, 1061 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 639cf14cd1..a8abf48441 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,19 @@ *2.3.0 [Edge]* +* Make ActionController#render(string) work as a shortcut for render :file/:template/:action => string. [#1435] [Pratik Naik] Examples: + + # Instead of render(:action => 'other_action') + render('other_action') # argument has no '/' + render(:other_action) + + # Instead of render(:template => 'controller/action') + render('controller/action') # argument must not begin with a '/', but contain a '/' + + # Instead of render(:file => '/Users/lifo/home.html.erb') + render('/Users/lifo/home.html.erb') # argument must begin with a '/' + +* Add :prompt option to date/time select helpers. #561 [Sam Oliver] + * Fixed that send_file shouldn't set an etag #1578 [Hongli Lai] * Allow users to opt out of the spoofing checks in Request#remote_ip. Useful for sites whose traffic regularly triggers false positives. [Darren Boyd] diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ae947820b4..98fb490d64 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -38,7 +38,7 @@ module ActionController # TODO: Review explicit to see if they will automatically be handled by # the initilizer if they are really needed. def self.load_all! - [Base, CGIHandler, CgiRequest, RackRequest, RackRequest, Http::Headers, UrlRewriter, UrlWriter] + [Base, CGIHandler, CgiRequest, Request, Response, Http::Headers, UrlRewriter, UrlWriter] end autoload :AbstractRequest, 'action_controller/request' @@ -59,7 +59,11 @@ module ActionController autoload :MiddlewareStack, 'action_controller/middleware_stack' autoload :MimeResponds, 'action_controller/mime_responds' autoload :PolymorphicRoutes, 'action_controller/polymorphic_routes' - autoload :RackRequest, 'action_controller/rack_process' + autoload :Request, 'action_controller/request' + autoload :RequestParser, 'action_controller/request_parser' + autoload :UrlEncodedPairParser, 'action_controller/url_encoded_pair_parser' + autoload :UploadedStringIO, 'action_controller/uploaded_file' + autoload :UploadedTempfile, 'action_controller/uploaded_file' autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Response, 'action_controller/response' autoload :RequestForgeryProtection, 'action_controller/request_forgery_protection' @@ -74,6 +78,7 @@ module ActionController autoload :Translation, 'action_controller/translation' autoload :UrlRewriter, 'action_controller/url_rewriter' autoload :UrlWriter, 'action_controller/url_rewriter' + autoload :VerbPiggybacking, 'action_controller/verb_piggybacking' autoload :Verification, 'action_controller/verification' module Assertions diff --git a/actionpack/lib/action_controller/assertions/routing_assertions.rb b/actionpack/lib/action_controller/assertions/routing_assertions.rb index 8a837c592c..5101751cea 100644 --- a/actionpack/lib/action_controller/assertions/routing_assertions.rb +++ b/actionpack/lib/action_controller/assertions/routing_assertions.rb @@ -134,7 +134,7 @@ module ActionController path = "/#{path}" unless path.first == '/' # Assume given controller - request = ActionController::TestRequest.new({}, {}, nil) + request = ActionController::TestRequest.new request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method request.path = path diff --git a/actionpack/lib/action_controller/assertions/selector_assertions.rb b/actionpack/lib/action_controller/assertions/selector_assertions.rb index 248ca85994..7f8fe9ab19 100644 --- a/actionpack/lib/action_controller/assertions/selector_assertions.rb +++ b/actionpack/lib/action_controller/assertions/selector_assertions.rb @@ -402,6 +402,7 @@ module ActionController if rjs_type if rjs_type == :insert position = args.shift + id = args.shift insertion = "insert_#{position}".to_sym raise ArgumentError, "Unknown RJS insertion type #{position}" unless RJS_STATEMENTS[insertion] statement = "(#{RJS_STATEMENTS[insertion]})" diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index eae17d6dd5..da3d1f46ee 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -254,7 +254,7 @@ module ActionController #:nodoc: cattr_reader :protected_instance_variables # Controller specific instance variables which will not be accessible inside views. @@protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller - @action_name @before_filter_chain_aborted @action_cache_path @_session @_cookies @_headers @_params + @action_name @before_filter_chain_aborted @action_cache_path @_session @_headers @_params @_flash @_response) # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, @@ -382,6 +382,13 @@ module ActionController #:nodoc: attr_accessor :action_name class << self + def call(env) + # HACK: For global rescue to have access to the original request and response + request = env["actioncontroller.rescue.request"] ||= Request.new(env) + response = env["actioncontroller.rescue.response"] ||= Response.new + process(request, response) + end + # Factory for the standard create, process loop where the controller is discarded after processing. def process(request, response) #:nodoc: new.process(request, response) @@ -502,7 +509,7 @@ module ActionController #:nodoc: protected :filter_parameters end - delegate :exempt_from_layout, :to => 'ActionView::Base' + delegate :exempt_from_layout, :to => 'ActionView::Template' end public @@ -859,16 +866,23 @@ module ActionController #:nodoc: def render(options = nil, extra_options = {}, &block) #:doc: raise DoubleRenderError, "Can only render or redirect once per action" if performed? + validate_render_arguments(options, extra_options, block_given?) + if options.nil? - return render(:file => default_template_name, :layout => true) - elsif !extra_options.is_a?(Hash) - raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" - else - if options == :update - options = extra_options.merge({ :update => true }) - elsif !options.is_a?(Hash) - raise RenderError, "You called render with invalid options : #{options.inspect}" + options = { :template => default_template.filename, :layout => true } + elsif options == :update + options = extra_options.merge({ :update => true }) + elsif options.is_a?(String) || options.is_a?(Symbol) + case options.to_s.index('/') + when 0 + extra_options[:file] = options + when nil + extra_options[:action] = options + else + extra_options[:template] = options end + + options = extra_options end layout = pick_layout(options) @@ -898,7 +912,7 @@ module ActionController #:nodoc: render_for_text(@template.render(options.merge(:layout => layout)), options[:status]) elsif action_name = options[:action] - render_for_file(default_template_name(action_name.to_s), options[:status], layout) + render_for_file(default_template(action_name.to_s), options[:status], layout) elsif xml = options[:xml] response.content_type ||= Mime::XML @@ -933,7 +947,7 @@ module ActionController #:nodoc: render_for_text(nil, options[:status]) else - render_for_file(default_template_name, options[:status], layout) + render_for_file(default_template, options[:status], layout) end end end @@ -1164,7 +1178,8 @@ module ActionController #:nodoc: private def render_for_file(template_path, status = nil, layout = nil, locals = {}) #:nodoc: - logger.info("Rendering #{template_path}" + (status ? " (#{status})" : '')) if logger + path = template_path.respond_to?(:path_without_format_and_extension) ? template_path.path_without_format_and_extension : template_path + logger.info("Rendering #{path}" + (status ? " (#{status})" : '')) if logger render_for_text @template.render(:file => template_path, :locals => locals, :layout => layout), status end @@ -1185,6 +1200,16 @@ module ActionController #:nodoc: end end + def validate_render_arguments(options, extra_options, has_block) + if options && (has_block && options != :update) && !options.is_a?(String) && !options.is_a?(Hash) && !options.is_a?(Symbol) + raise RenderError, "You called render with invalid options : #{options.inspect}" + end + + if !extra_options.is_a?(Hash) + raise RenderError, "You called render with invalid options : #{options.inspect}, #{extra_options.inspect}" + end + end + def initialize_template_class(response) response.template = ActionView::Base.new(self.class.view_paths, {}, self) response.template.helpers.send :include, self.class.master_helper_module @@ -1193,7 +1218,7 @@ module ActionController #:nodoc: end def assign_shortcuts(request, response) - @_request, @_params, @_cookies = request, request.parameters, request.cookies + @_request, @_params = request, request.parameters @_response = response @_response.session = request.session @@ -1241,10 +1266,17 @@ module ActionController #:nodoc: elsif respond_to? :method_missing method_missing action_name default_render unless performed? - elsif template_exists? - default_render else - raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller + begin + default_render + rescue ActionView::MissingTemplate => e + # Was the implicit template missing, or was it another template? + if e.path == default_template_name + raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence}", caller + else + raise e + end + end end end @@ -1290,10 +1322,8 @@ module ActionController #:nodoc: @_session.close if @_session && @_session.respond_to?(:close) end - def template_exists?(template_name = default_template_name) - @template.send(:_pick_template, template_name) ? true : false - rescue ActionView::MissingTemplate - false + def default_template(action_name = self.action_name) + self.view_paths.find_template(default_template_name(action_name), default_template_format) end def default_template_name(action_name = self.action_name) diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index b4d251eb3c..1d14df0052 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -27,7 +27,6 @@ module ActionController #:nodoc: autoload :Actions, 'action_controller/caching/actions' autoload :Fragments, 'action_controller/caching/fragments' autoload :Pages, 'action_controller/caching/pages' - autoload :SqlCache, 'action_controller/caching/sql_cache' autoload :Sweeping, 'action_controller/caching/sweeping' def self.included(base) #:nodoc: @@ -41,7 +40,7 @@ module ActionController #:nodoc: end include Pages, Actions, Fragments - include Sweeping, SqlCache if defined?(ActiveRecord) + include Sweeping if defined?(ActiveRecord) @@perform_caching = true cattr_accessor :perform_caching diff --git a/actionpack/lib/action_controller/caching/sql_cache.rb b/actionpack/lib/action_controller/caching/sql_cache.rb deleted file mode 100644 index 139be6100d..0000000000 --- a/actionpack/lib/action_controller/caching/sql_cache.rb +++ /dev/null @@ -1,18 +0,0 @@ -module ActionController #:nodoc: - module Caching - module SqlCache - def self.included(base) #:nodoc: - if defined?(ActiveRecord) && ActiveRecord::Base.respond_to?(:cache) - base.alias_method_chain :perform_action, :caching - end - end - - protected - def perform_action_with_caching - ActiveRecord::Base.cache do - perform_action_without_caching - end - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb index 0e058085ec..840ceb5abd 100644 --- a/actionpack/lib/action_controller/cookies.rb +++ b/actionpack/lib/action_controller/cookies.rb @@ -64,45 +64,31 @@ module ActionController #:nodoc: # Returns the value of the cookie by +name+, or +nil+ if no such cookie exists. def [](name) - cookie = @cookies[name.to_s] - if cookie && cookie.respond_to?(:value) - cookie.size > 1 ? cookie.value : cookie.value[0] - else - cookie - end + super(name.to_s) end # Sets the cookie named +name+. The second argument may be the very cookie # value, or a hash of options as documented above. - def []=(name, options) + def []=(key, options) if options.is_a?(Hash) - options = options.inject({}) { |options, pair| options[pair.first.to_s] = pair.last; options } - options["name"] = name.to_s + options.symbolize_keys! else - options = { "name" => name.to_s, "value" => options } + options = { :value => options } end - set_cookie(options) + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s, options[:value]) + @controller.response.set_cookie(key, options) end # Removes the cookie on the client machine by setting the value to an empty string # and setting its expiration date into the past. Like <tt>[]=</tt>, you can pass in # an options hash to delete cookies with extra data such as a <tt>:path</tt>. - def delete(name, options = {}) - options.stringify_keys! - set_cookie(options.merge("name" => name.to_s, "value" => "", "expires" => Time.at(0))) + def delete(key, options = {}) + options.symbolize_keys! + options[:path] = "/" unless options.has_key?(:path) + super(key.to_s) + @controller.response.delete_cookie(key, options) end - - private - # Builds a CGI::Cookie object and adds the cookie to the response headers. - # - # The path of the cookie defaults to "/" if there's none in +options+, and - # everything is passed to the CGI::Cookie constructor. - def set_cookie(options) #:doc: - options["path"] = "/" unless options["path"] - cookie = CGI::Cookie.new(options) - @controller.logger.info "Cookie set: #{cookie}" unless @controller.logger.nil? - @controller.response.headers["cookie"] << cookie - end end end diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index e1eaaf7cbb..c4e7357b81 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -44,22 +44,8 @@ module ActionController cattr_accessor :middleware self.middleware = MiddlewareStack.new do |middleware| - middleware.use "ActionController::Lock", :if => lambda { - !ActionController::Base.allow_concurrency - } - middleware.use "ActionController::Failsafe" - - ["ActionController::Session::CookieStore", - "ActionController::Session::MemCacheStore", - "ActiveRecord::SessionStore"].each do |store| - middleware.use(store, ActionController::Base.session_options, - :if => lambda { - if session_store = ActionController::Base.session_store - session_store.name == store - end - } - ) - end + middlewares = File.join(File.dirname(__FILE__), "middlewares.rb") + middleware.instance_eval(File.read(middlewares)) end include ActiveSupport::Callbacks @@ -74,11 +60,10 @@ module ActionController def dispatch begin run_callbacks :before_dispatch - controller = Routing::Routes.recognize(@request) - controller.process(@request, @response).to_a + Routing::Routes.call(@env) rescue Exception => exception if controller ||= (::ApplicationController rescue Base) - controller.process_with_exception(@request, @response, exception).to_a + controller.call_with_exception(@env, exception).to_a else raise exception end @@ -97,8 +82,7 @@ module ActionController end def _call(env) - @request = RackRequest.new(env) - @response = Response.new + @env = env dispatch end @@ -124,8 +108,7 @@ module ActionController def checkin_connections # Don't return connection (and peform implicit rollback) if this request is a part of integration test - # TODO: This callback should have direct access to env - return if @request.key?("rack.test") + return if @env.key?("rack.test") ActiveRecord::Base.clear_active_connections! end end diff --git a/actionpack/lib/action_controller/helpers.rb b/actionpack/lib/action_controller/helpers.rb index 402750c57d..ba65032f6a 100644 --- a/actionpack/lib/action_controller/helpers.rb +++ b/actionpack/lib/action_controller/helpers.rb @@ -163,9 +163,9 @@ module ActionController #:nodoc: def helper_method(*methods) methods.flatten.each do |method| master_helper_module.module_eval <<-end_eval - def #{method}(*args, &block) - controller.send(%(#{method}), *args, &block) - end + def #{method}(*args, &block) # def current_user(*args, &block) + controller.send(%(#{method}), *args, &block) # controller.send(%(current_user), *args, &block) + end # end end_eval end end diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb index 2ed810db7d..3cb5829eca 100644 --- a/actionpack/lib/action_controller/http_authentication.rb +++ b/actionpack/lib/action_controller/http_authentication.rb @@ -55,7 +55,31 @@ module ActionController # end # end # - # + # Simple Digest example. Note the block must return the user's password so the framework + # can appropriately hash it to check the user's credentials. Returning nil will cause authentication to fail. + # + # class PostsController < ApplicationController + # Users = {"dhh" => "secret"} + # + # before_filter :authenticate, :except => [ :index ] + # + # def index + # render :text => "Everyone can see me!" + # end + # + # def edit + # render :text => "I'm only accessible if you know the password" + # end + # + # private + # def authenticate + # authenticate_or_request_with_http_digest(realm) do |user_name| + # Users[user_name] + # end + # end + # end + # + # # In your integration tests, you can do something like this: # # def test_access_granted_from_xml @@ -108,7 +132,10 @@ module ActionController end def decode_credentials(request) - ActiveSupport::Base64.decode64(authorization(request).split.last || '') + # Properly decode credentials spanning a new-line + auth = authorization(request) + auth.slice!('Basic ') + ActiveSupport::Base64.decode64(auth || '') end def encode_credentials(user_name, password) @@ -120,5 +147,165 @@ module ActionController controller.__send__ :render, :text => "HTTP Basic: Access denied.\n", :status => :unauthorized end end + + module Digest + extend self + + module ControllerMethods + def authenticate_or_request_with_http_digest(realm = "Application", &password_procedure) + begin + authenticate_with_http_digest!(realm, &password_procedure) + rescue ActionController::HttpAuthentication::Error => e + msg = e.message + msg = "#{msg} expected '#{e.expected}' was '#{e.was}'" unless e.expected.nil? + raise msg if e.fatal? + request_http_digest_authentication(realm, msg) + end + end + + # Authenticate using HTTP Digest, throwing ActionController::HttpAuthentication::Error on failure. + # This allows more detailed analysis of authentication failures + # to be relayed to the client. + def authenticate_with_http_digest!(realm = "Application", &login_procedure) + HttpAuthentication::Digest.authenticate(self, realm, &login_procedure) + end + + # Authenticate with HTTP Digest, returns true or false + def authenticate_with_http_digest(realm = "Application", &login_procedure) + HttpAuthentication::Digest.authenticate(self, realm, &login_procedure) rescue false + end + + # Render output including the HTTP Digest authentication header + def request_http_digest_authentication(realm = "Application", message = nil) + HttpAuthentication::Digest.authentication_request(self, realm, message) + end + + # Add HTTP Digest authentication header to result headers + def http_digest_authentication_header(realm = "Application") + HttpAuthentication::Digest.authentication_header(self, realm) + end + end + + # Raises error unless authentictaion succeeds, returns true otherwise + def authenticate(controller, realm, &password_procedure) + raise Error.new(false), "No authorization header found" unless authorization(controller.request) + validate_digest_response(controller, realm, &password_procedure) + true + end + + def authorization(request) + request.env['HTTP_AUTHORIZATION'] || + request.env['X-HTTP_AUTHORIZATION'] || + request.env['X_HTTP_AUTHORIZATION'] || + request.env['REDIRECT_X_HTTP_AUTHORIZATION'] + end + + # Raises error unless the request credentials response value matches the expected value. + def validate_digest_response(controller, realm, &password_procedure) + credentials = decode_credentials(controller.request) + + # Check the nonce, opaque and realm. + # Ignore nc, as we have no way to validate the number of times this nonce has been used + validate_nonce(controller.request, credentials[:nonce]) + raise Error.new(false, realm, credentials[:realm]), "Realm doesn't match" unless realm == credentials[:realm] + raise Error.new(true, opaque(controller.request), credentials[:opaque]),"Opaque doesn't match" unless opaque(controller.request) == credentials[:opaque] + + password = password_procedure.call(credentials[:username]) + raise Error.new(false), "No password" if password.nil? + expected = expected_response(controller.request.env['REQUEST_METHOD'], controller.request.url, credentials, password) + raise Error.new(false, expected, credentials[:response]), "Invalid response" unless expected == credentials[:response] + end + + # Returns the expected response for a request of +http_method+ to +uri+ with the decoded +credentials+ and the expected +password+ + def expected_response(http_method, uri, credentials, password) + ha1 = ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + ha2 = ::Digest::MD5.hexdigest([http_method.to_s.upcase,uri].join(':')) + ::Digest::MD5.hexdigest([ha1,credentials[:nonce], credentials[:nc], credentials[:cnonce],credentials[:qop],ha2].join(':')) + end + + def encode_credentials(http_method, credentials, password) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password) + "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') + end + + def decode_credentials(request) + authorization(request).to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair| + key, value = pair.split('=', 2) + hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '') + hash + end + end + + def authentication_header(controller, realm) + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(controller.request)}", opaque="#{opaque(controller.request)}") + end + + def authentication_request(controller, realm, message = "HTTP Digest: Access denied") + authentication_header(controller, realm) + controller.send! :render, :text => message, :status => :unauthorized + end + + # Uses an MD5 digest based on time to generate a value to be used only once. + # + # A server-specified data string which should be uniquely generated each time a 401 response is made. + # It is recommended that this string be base64 or hexadecimal data. + # Specifically, since the string is passed in the header lines as a quoted string, the double-quote character is not allowed. + # + # The contents of the nonce are implementation dependent. + # The quality of the implementation depends on a good choice. + # A nonce might, for example, be constructed as the base 64 encoding of + # + # => time-stamp H(time-stamp ":" ETag ":" private-key) + # + # where time-stamp is a server-generated time or other non-repeating value, + # ETag is the value of the HTTP ETag header associated with the requested entity, + # and private-key is data known only to the server. + # With a nonce of this form a server would recalculate the hash portion after receiving the client authentication header and + # reject the request if it did not match the nonce from that header or + # if the time-stamp value is not recent enough. In this way the server can limit the time of the nonce's validity. + # The inclusion of the ETag prevents a replay request for an updated version of the resource. + # (Note: including the IP address of the client in the nonce would appear to offer the server the ability + # to limit the reuse of the nonce to the same client that originally got it. + # However, that would break proxy farms, where requests from a single user often go through different proxies in the farm. + # Also, IP address spoofing is not that hard.) + # + # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to + # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for + # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 + # of this document. + # + # The nonce is opaque to the client. + def nonce(request, time = Time.now) + session_id = request.is_a?(String) ? request : request.session.session_id + t = time.to_i + hashed = [t, session_id] + digest = ::Digest::MD5.hexdigest(hashed.join(":")) + Base64.encode64("#{t}:#{digest}").gsub("\n", '') + end + + def validate_nonce(request, value) + t = Base64.decode64(value).split(":").first.to_i + raise Error.new(true), "Stale Nonce" if (t - Time.now.to_i).abs > 10 * 60 + n = nonce(request, t) + raise Error.new(true, value, n), "Bad Nonce" unless n == value + end + + # Opaque based on digest of session_id + def opaque(request) + session_id = request.is_a?(String) ? request : request.session.session_id + @opaque ||= Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '') + end + end + + class Error < RuntimeError + attr_accessor :expected, :was + def initialize(fatal = false, expected = nil, was = nil) + @fatal = fatal + @expected = expected + @was = was + end + + def fatal?; @fatal; end + end end end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index d952c3489b..a8e54c2fc7 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -2,6 +2,17 @@ require 'stringio' require 'uri' require 'active_support/test_case' +# Monkey patch Rack::Lint to support rewind +module Rack + class Lint + class InputWrapper + def rewind + @input.rewind + end + end + end +end + module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses @@ -57,6 +68,15 @@ module ActionController # A running counter of the number of requests processed. attr_accessor :request_count + # Nonce value for Digest Authentication, implicitly set on response with WWW-Authentication + attr_accessor :nonce + + # Opaque value for Digest Authentication, implicitly set on response with WWW-Authentication + attr_accessor :opaque + + # Opaque value for Authentication, implicitly set on response with WWW-Authentication + attr_accessor :realm + class MultiPartNeededException < Exception end @@ -232,6 +252,53 @@ module ActionController end alias xhr :xml_http_request + def request_with_noauth(http_method, uri, parameters, headers) + process_with_auth http_method, uri, parameters, headers + end + + # Performs a request with the given http_method and parameters, including HTTP Basic authorization headers. + # See get() for more details on paramters and headers. + # + # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_basic, #post_with_basic, + # #put_with_basic, #delete_with_basic, and #head_with_basic. + def request_with_basic(http_method, uri, parameters, headers, user_name, password) + process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Basic.encode_credentials(user_name, password)) + end + + # Performs a request with the given http_method and parameters, including HTTP Digest authorization headers. + # See get() for more details on paramters and headers. + # + # You can perform GET, POST, PUT, DELETE, and HEAD requests with #get_with_digest, #post_with_digest, + # #put_with_digest, #delete_with_digest, and #head_with_digest. + def request_with_digest(http_method, uri, parameters, headers, user_name, password) + # Realm, Nonce, and Opaque taken from previoius 401 response + + credentials = { + :username => user_name, + :realm => @realm, + :nonce => @nonce, + :qop => "auth", + :nc => "00000001", + :cnonce => "0a4f113b", + :opaque => @opaque, + :uri => uri + } + + raise "Digest request without previous 401 response" if @opaque.nil? + + process_with_auth http_method, uri, parameters, headers.merge(:authorization => ActionController::HttpAuthentication::Digest.encode_credentials(http_method, credentials, password)) + end + + # def get_with_basic, def post_with_basic, def put_with_basic, def delete_with_basic, def head_with_basic + # def get_with_digest, def post_with_digest, def put_with_digest, def delete_with_digest, def head_with_digest + [:get, :post, :put, :delete, :head].each do |method| + [:noauth, :basic, :digest].each do |auth_type| + define_method("#{method}_with_#{auth_type}") do |uri, parameters, headers, *auth| + send("request_with_#{auth_type}", method, uri, parameters, headers, *auth) + end + end + end + # Returns the URL for the given options, according to the rules specified # in the application's routes. def url_for(options) @@ -353,6 +420,32 @@ module ActionController return status end + # Same as process, but handles authentication returns to perform + # Basic or Digest authentication + def process_with_auth(method, path, parameters = nil, headers = nil) + status = process(method, path, parameters, headers) + + if status == 401 + # Extract authentication information from response + auth_data = @response.headers['WWW-Authenticate'] + if /^Basic /.match(auth_data) + # extract realm, to be used in subsequent request + @realm = auth_header.split(' ')[1] + elsif /^Digest/.match(auth_data) + creds = auth_data.to_s.gsub(/^Digest\s+/,'').split(',').inject({}) do |hash, pair| + key, value = pair.split('=', 2) + hash[key.strip.to_sym] = value.to_s.gsub(/^"|"$/,'').gsub(/'/, '') + hash + end + @realm = creds[:realm] + @nonce = creds[:nonce] + @opaque = creds[:opaque] + end + end + + return status + end + # Encode the cookies hash in a format suitable for passing to a # request. def encode_cookies @@ -371,7 +464,7 @@ module ActionController "SERVER_PORT" => https? ? "443" : "80", "HTTPS" => https? ? "on" : "off" } - UrlRewriter.new(RackRequest.new(env), {}) + UrlRewriter.new(Request.new(env), {}) end def name_with_prefix(prefix, name) diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index 54108df06d..159c5c7326 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -178,9 +178,15 @@ module ActionController #:nodoc: find_layout(layout, format) end + def layout_list #:nodoc: + Array(view_paths).sum([]) { |path| Dir["#{path}/layouts/**/*"] } + end + def find_layout(layout, *formats) #:nodoc: return layout if layout.respond_to?(:render) view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) + rescue ActionView::MissingTemplate + nil end private @@ -188,7 +194,7 @@ module ActionController #:nodoc: inherited_without_layout(child) unless child.name.blank? layout_match = child.name.underscore.sub(/_controller$/, '').sub(/^controllers\//, '') - child.layout(layout_match, {}, true) if child.find_layout(layout_match, :all) + child.layout(layout_match, {}, true) unless child.layout_list.grep(%r{layouts/#{layout_match}(\.[a-z][0-9a-z]*)+$}).empty? end end @@ -225,8 +231,16 @@ module ActionController #:nodoc: private def candidate_for_layout?(options) - options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? && - !@template.__send__(:_exempt_from_layout?, options[:template] || default_template_name(options[:action])) + template = options[:template] || default_template(options[:action]) + if options.values_at(:text, :xml, :json, :file, :inline, :partial, :nothing, :update).compact.empty? + begin + !self.view_paths.find_template(template, default_template_format).exempt_from_layout? + rescue ActionView::MissingTemplate + true + end + end + rescue ActionView::MissingTemplate + false end def pick_layout(options) @@ -235,7 +249,7 @@ module ActionController #:nodoc: when FalseClass nil when NilClass, TrueClass - active_layout if action_has_layout? && !@template.__send__(:_exempt_from_layout?, default_template_name) + active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name) else active_layout(layout) end diff --git a/actionpack/lib/action_controller/middlewares.rb b/actionpack/lib/action_controller/middlewares.rb new file mode 100644 index 0000000000..793739723f --- /dev/null +++ b/actionpack/lib/action_controller/middlewares.rb @@ -0,0 +1,21 @@ +use "ActionController::Lock", :if => lambda { + !ActionController::Base.allow_concurrency +} + +use "ActionController::Failsafe" + +use "ActiveRecord::QueryCache", :if => lambda { defined?(ActiveRecord) } + +["ActionController::Session::CookieStore", + "ActionController::Session::MemCacheStore", + "ActiveRecord::SessionStore"].each do |store| + use(store, ActionController::Base.session_options, + :if => lambda { + if session_store = ActionController::Base.session_store + session_store.name == store + end + } + ) +end + +use ActionController::VerbPiggybacking diff --git a/actionpack/lib/action_controller/mime_responds.rb b/actionpack/lib/action_controller/mime_responds.rb index 29294476f7..b755363873 100644 --- a/actionpack/lib/action_controller/mime_responds.rb +++ b/actionpack/lib/action_controller/mime_responds.rb @@ -143,12 +143,27 @@ module ActionController #:nodoc: custom(@mime_type_priority.first, &block) end end + + def self.generate_method_for_mime(mime) + sym = mime.is_a?(Symbol) ? mime : mime.to_sym + const = sym.to_s.upcase + class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{sym}(&block) # def html(&block) + custom(Mime::#{const}, &block) # custom(Mime::HTML, &block) + end # end + RUBY + end - def method_missing(symbol, &block) - mime_constant = symbol.to_s.upcase + Mime::SET.each do |mime| + generate_method_for_mime(mime) + end - if Mime::SET.include?(Mime.const_get(mime_constant)) - custom(Mime.const_get(mime_constant), &block) + def method_missing(symbol, &block) + mime_constant = Mime.const_get(symbol.to_s.upcase) + + if Mime::SET.include?(mime_constant) + self.class.generate_method_for_mime(mime_constant) + send(symbol, &block) else super end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index dce50c6c3b..924d1aa6bd 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -118,13 +118,17 @@ module ActionController %w(edit new).each do |action| module_eval <<-EOT, __FILE__, __LINE__ - def #{action}_polymorphic_url(record_or_hash, options = {}) - polymorphic_url(record_or_hash, options.merge(:action => "#{action}")) - end - - def #{action}_polymorphic_path(record_or_hash, options = {}) - polymorphic_url(record_or_hash, options.merge(:action => "#{action}", :routing_type => :path)) - end + def #{action}_polymorphic_url(record_or_hash, options = {}) # def edit_polymorphic_url(record_or_hash, options = {}) + polymorphic_url( # polymorphic_url( + record_or_hash, # record_or_hash, + options.merge(:action => "#{action}")) # options.merge(:action => "edit")) + end # end + # + def #{action}_polymorphic_path(record_or_hash, options = {}) # def edit_polymorphic_path(record_or_hash, options = {}) + polymorphic_url( # polymorphic_url( + record_or_hash, # record_or_hash, + options.merge(:action => "#{action}", :routing_type => :path)) # options.merge(:action => "edit", :routing_type => :path)) + end # end EOT end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb deleted file mode 100644 index 8c6db91dd0..0000000000 --- a/actionpack/lib/action_controller/rack_process.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'action_controller/cgi_ext' - -module ActionController #:nodoc: - class RackRequest < AbstractRequest #:nodoc: - attr_accessor :session_options - - class SessionFixationAttempt < StandardError #:nodoc: - end - - def initialize(env) - @env = env - super() - end - - %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO - PATH_TRANSLATED REMOTE_HOST - REMOTE_IDENT REMOTE_USER SCRIPT_NAME - SERVER_NAME SERVER_PROTOCOL - - HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING - 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 - - def query_string - qs = super - if !qs.blank? - qs - else - @env['QUERY_STRING'] - end - end - - def body_stream #:nodoc: - @env['rack.input'] - end - - def key?(key) - @env.key?(key) - end - - def cookies - Rack::Request.new(@env).cookies - end - - def server_port - @env['SERVER_PORT'].to_i - end - - def server_software - @env['SERVER_SOFTWARE'].split("/").first - end - - def session_options - @env['rack.session.options'] ||= {} - end - - def session_options=(options) - @env['rack.session.options'] = options - end - - def session - @env['rack.session'] ||= {} - end - - def reset_session - @env['rack.session'] = {} - end - end -end diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 087fffe87d..822955d1db 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -3,32 +3,48 @@ require 'stringio' require 'strscan' require 'active_support/memoizable' +require 'action_controller/cgi_ext' module ActionController # CgiRequest and TestRequest provide concrete implementations. - class AbstractRequest + class Request extend ActiveSupport::Memoizable - def self.relative_url_root=(relative_url_root) - ActiveSupport::Deprecation.warn( - "ActionController::AbstractRequest.relative_url_root= has been renamed." + - "You can now set it with config.action_controller.relative_url_root=", caller) - ActionController::Base.relative_url_root=relative_url_root + class SessionFixationAttempt < StandardError #:nodoc: end - 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 + def initialize(env) + @env = env + end + + %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO + PATH_TRANSLATED REMOTE_HOST + REMOTE_IDENT REMOTE_USER SCRIPT_NAME + SERVER_NAME SERVER_PROTOCOL + + HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING + 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 + + def key?(key) + @env.key?(key) + end + + 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 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 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 @@ -85,7 +101,7 @@ 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(content_type_without_parameters) + Mime::Type.lookup(parser.content_type_without_parameters) end memoize :content_type @@ -125,15 +141,15 @@ module ActionController # supplied, both must match, or the request is not considered fresh. def fresh?(response) case - when if_modified_since && if_none_match - not_modified?(response.last_modified) && etag_matches?(response.etag) - when if_modified_since - not_modified?(response.last_modified) - when if_none_match - etag_matches?(response.etag) - else - false - end + when if_modified_since && if_none_match + not_modified?(response.last_modified) && etag_matches?(response.etag) + when if_modified_since + not_modified?(response.last_modified) + when if_none_match + etag_matches?(response.etag) + else + false + end end # Returns the Mime type for the \format used in the request. @@ -248,7 +264,6 @@ EOM end memoize :server_software - # Returns the complete URL used for this request. def url protocol + host_with_port + request_uri @@ -271,7 +286,7 @@ EOM if forwarded = env["HTTP_X_FORWARDED_HOST"] forwarded.split(/,\s?/).last else - env['HTTP_HOST'] || env['SERVER_NAME'] || "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" end end @@ -332,11 +347,7 @@ EOM # Returns the query string, accounting for server idiosyncrasies. def query_string - if uri = @env['REQUEST_URI'] - uri.split('?', 2)[1] || '' - else - @env['QUERY_STRING'] || '' - end + @env['QUERY_STRING'].present? ? @env['QUERY_STRING'] : (@env['REQUEST_URI'].split('?', 2)[1] || '') end memoize :query_string @@ -378,11 +389,7 @@ EOM # Read the request \body. This is useful for web services that need to # work with raw requests directly. 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'] + parser.raw_post end # Returns both GET and POST \parameters in a single hash. @@ -391,7 +398,7 @@ EOM end def path_parameters=(parameters) #:nodoc: - @path_parameters = parameters + @env["rack.routing_args"] = parameters @symbolized_path_parameters = @parameters = nil end @@ -407,18 +414,11 @@ EOM # # See <tt>symbolized_path_parameters</tt> for symbolized keys. def path_parameters - @path_parameters ||= {} + @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 - 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 + parser.body end def remote_addr @@ -430,441 +430,53 @@ EOM end alias referer referrer - def query_parameters - @query_parameters ||= self.class.parse_query_parameters(query_string) + @query_parameters ||= parser.query_parameters end def request_parameters - @request_parameters ||= parse_formatted_request_parameters + @request_parameters ||= parser.request_parameters end - - #-- - # Must be implemented in the concrete request - #++ - def body_stream #:nodoc: + @env['rack.input'] end - def cookies #:nodoc: + def cookies + Rack::Request.new(@env).cookies end - def session #:nodoc: + def session + @env['rack.session'] ||= {} end def session=(session) #:nodoc: @session = session end - def reset_session #:nodoc: + def reset_session + @env['rack.session'] = {} end - protected - # 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 - - # 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 - memoize :content_type_without_parameters - - private - 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 - - 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 named_host?(host) - !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) - 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 + def session_options + @env['rack.session.options'] ||= {} end - end - class UrlEncodedPairParser < StringScanner #:nodoc: - attr_reader :top, :parent, :result - - def initialize(pairs = []) - super('') - @result = {} - pairs.each { |key, value| parse(key, value) } + def session_options=(options) + @env['rack.session.options'] = options end - KEY_REGEXP = %r{([^\[\]=&]+)} - BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} - - # Parse the query string - def parse(key, value) - self.string = key - @top, @parent = result, nil - - # First scan the bare key - key = scan(KEY_REGEXP) or return - key = post_key_check(key) - - # Then scan as many nestings as present - until eos? - r = scan(BRACKETED_KEY_REGEXP) or return - key = self[1] - key = post_key_check(key) - end - - bind(key, value) + def server_port + @env['SERVER_PORT'].to_i end private - # After we see a key, we must look ahead to determine our next action. Cases: - # - # [] follows the key. Then the value must be an array. - # = follows the key. (A value comes next) - # & or the end of string follows the key. Then the key is a flag. - # otherwise, a hash follows the key. - def post_key_check(key) - if scan(/\[\]/) # a[b][] indicates that b is an array - container(key, Array) - nil - elsif check(/\[[^\]]/) # a[b] indicates that a is a hash - container(key, Hash) - nil - else # End of key? We do nothing. - key - end - end - - # Add a container to the stack. - def container(key, klass) - type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) - value = bind(key, klass.new) - type_conflict! klass, value unless value.is_a?(klass) - push(value) - end - - # Push a value onto the 'stack', which is actually only the top 2 items. - def push(value) - @parent, @top = @top, value - end - - # Bind a key (which may be nil for items in an array) to the provided value. - def bind(key, value) - if top.is_a? Array - if key - if top[-1].is_a?(Hash) && ! top[-1].key?(key) - top[-1][key] = value - else - top << {key => value}.with_indifferent_access - push top.last - value = top[key] - end - else - top << value - end - elsif top.is_a? Hash - key = CGI.unescape(key) - parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) - top[key] ||= value - return top[key] - else - raise ArgumentError, "Don't know what to do: top is #{top.inspect}" - end - - return value - end - - def type_conflict!(klass, value) - raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)" - end - end - - module UploadedFile - def self.included(base) - base.class_eval do - attr_accessor :original_path, :content_type - alias_method :local_path, :path + def named_host?(host) + !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) 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 - # those which give the lone filename. - # The Windows regexp is adapted from Perl's File::Basename. - def original_filename - unless defined? @original_filename - @original_filename = - unless original_path.blank? - if original_path =~ /^(?:.*[:\\\/])?(.*)/m - $1 - else - File.basename original_path - end - end + def parser + @parser ||= ActionController::RequestParser.new(@env) end - @original_filename - end - end - - class UploadedStringIO < StringIO - include UploadedFile - end - - class UploadedTempfile < Tempfile - include UploadedFile end end diff --git a/actionpack/lib/action_controller/request_parser.rb b/actionpack/lib/action_controller/request_parser.rb new file mode 100644 index 0000000000..82ee4c84c4 --- /dev/null +++ b/actionpack/lib/action_controller/request_parser.rb @@ -0,0 +1,314 @@ +module ActionController + class RequestParser + def initialize(env) + @env = env + end + + def request_parameters + @request_parameters ||= parse_formatted_request_parameters + end + + def query_parameters + @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 + @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/rescue.rb b/actionpack/lib/action_controller/rescue.rb index 5ef79a36ce..3a5e5071bb 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -59,7 +59,9 @@ module ActionController #:nodoc: end module ClassMethods - def process_with_exception(request, response, exception) #:nodoc: + def call_with_exception(env, exception) #:nodoc: + request = env["actioncontroller.rescue.request"] + response = env["actioncontroller.rescue.response"] new.process(request, response, :rescue_action, exception) end end diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index 866616bac3..64319fe102 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -34,14 +34,14 @@ module ActionController # :nodoc: DEFAULT_HEADERS = { "Cache-Control" => "no-cache" } attr_accessor :request - attr_accessor :session, :cookies, :assigns, :template, :layout + attr_accessor :session, :assigns, :template, :layout attr_accessor :redirected_to, :redirected_to_method_params delegate :default_charset, :to => 'ActionController::Base' def initialize @status = 200 - @header = DEFAULT_HEADERS.merge("cookie" => []) + @header = DEFAULT_HEADERS.dup @writer = lambda { |x| @body << x } @block = nil @@ -143,10 +143,9 @@ module ActionController # :nodoc: handle_conditional_get! set_content_length! convert_content_type! - convert_language! convert_expires! - set_cookies! + convert_cookies! end def each(&callback) @@ -168,6 +167,35 @@ module ActionController # :nodoc: str end + # Over Rack::Response#set_cookie to add HttpOnly option + def set_cookie(key, value) + case value + when Hash + domain = "; domain=" + value[:domain] if value[:domain] + path = "; path=" + value[:path] if value[:path] + # According to RFC 2109, we need dashes here. + # N.B.: cgi.rb uses spaces... + expires = "; expires=" + value[:expires].clone.gmtime. + strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires] + secure = "; secure" if value[:secure] + httponly = "; HttpOnly" if value[:http_only] + value = value[:value] + end + value = [value] unless Array === value + cookie = ::Rack::Utils.escape(key) + "=" + + value.map { |v| ::Rack::Utils.escape v }.join("&") + + "#{domain}#{path}#{expires}#{secure}#{httponly}" + + case self["Set-Cookie"] + when Array + self["Set-Cookie"] << cookie + when String + self["Set-Cookie"] = [self["Set-Cookie"], cookie] + when nil + self["Set-Cookie"] = cookie + end + end + private def handle_conditional_get! if etag? || last_modified? @@ -217,22 +245,8 @@ module ActionController # :nodoc: headers["Expires"] = headers.delete("") if headers["expires"] end - def set_cookies! - # Convert 'cookie' header to 'Set-Cookie' headers. - # Because Set-Cookie header can appear more the once in the response body, - # we store it in a line break separated string that will be translated to - # multiple Set-Cookie header by the handler. - if cookie = headers.delete('cookie') - cookies = [] - - case cookie - when Array then cookie.each { |c| cookies << c.to_s } - when Hash then cookie.each { |_, c| cookies << c.to_s } - else cookies << cookie.to_s - end - - headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact - end + def convert_cookies! + headers['Set-Cookie'] = Array(headers['Set-Cookie']).compact end end end diff --git a/actionpack/lib/action_controller/routing/route_set.rb b/actionpack/lib/action_controller/routing/route_set.rb index 13646aef61..06aef6e169 100644 --- a/actionpack/lib/action_controller/routing/route_set.rb +++ b/actionpack/lib/action_controller/routing/route_set.rb @@ -145,10 +145,10 @@ module ActionController def define_hash_access(route, name, kind, options) selector = hash_access_name(name, kind) named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(options = nil) - options ? #{options.inspect}.merge(options) : #{options.inspect} - end - protected :#{selector} + def #{selector}(options = nil) # def hash_for_users_url(options = nil) + options ? #{options.inspect}.merge(options) : #{options.inspect} # options ? {:only_path=>false}.merge(options) : {:only_path=>false} + end # end + protected :#{selector} # protected :hash_for_users_url end_eval helpers << selector end @@ -173,32 +173,33 @@ module ActionController # foo_url(bar, baz, bang, :sort_by => 'baz') # named_helper_module_eval <<-end_eval # We use module_eval to avoid leaks - def #{selector}(*args) - - #{generate_optimisation_block(route, kind)} - - opts = if args.empty? || Hash === args.first - args.first || {} - else - options = args.extract_options! - args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| - h[k] = v - h - end - options.merge(args) - end - - url_for(#{hash_access_method}(opts)) - - end - #Add an alias to support the now deprecated formatted_* URL. - def formatted_#{selector}(*args) - ActiveSupport::Deprecation.warn( - "formatted_#{selector}() has been deprecated. please pass format to the standard" + - "#{selector}() method instead.", caller) - #{selector}(*args) - end - protected :#{selector} + def #{selector}(*args) # def users_url(*args) + # + #{generate_optimisation_block(route, kind)} # #{generate_optimisation_block(route, kind)} + # + opts = if args.empty? || Hash === args.first # opts = if args.empty? || Hash === args.first + args.first || {} # args.first || {} + else # else + options = args.extract_options! # options = args.extract_options! + args = args.zip(#{route.segment_keys.inspect}).inject({}) do |h, (v, k)| # args = args.zip([]).inject({}) do |h, (v, k)| + h[k] = v # h[k] = v + h # h + end # end + options.merge(args) # options.merge(args) + end # end + # + url_for(#{hash_access_method}(opts)) # url_for(hash_for_users_url(opts)) + # + end # end + #Add an alias to support the now deprecated formatted_* URL. # #Add an alias to support the now deprecated formatted_* URL. + def formatted_#{selector}(*args) # def formatted_users_url(*args) + ActiveSupport::Deprecation.warn( # ActiveSupport::Deprecation.warn( + "formatted_#{selector}() has been deprecated. " + # "formatted_users_url() has been deprecated. " + + "please pass format to the standard" + # "please pass format to the standard" + + "#{selector}() method instead.", caller) # "users_url() method instead.", caller) + #{selector}(*args) # users_url(*args) + end # end + protected :#{selector} # protected :users_url end_eval helpers << selector end @@ -426,6 +427,12 @@ module ActionController end end + def call(env) + request = Request.new(env) + app = Routing::Routes.recognize(request) + app.call(env).to_a + end + def recognize(request) params = recognize_path(request.path, extract_request_environment(request)) request.path_parameters = params.with_indifferent_access diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb index 333fb61b45..e1786913a7 100644 --- a/actionpack/lib/action_controller/streaming.rb +++ b/actionpack/lib/action_controller/streaming.rb @@ -24,7 +24,8 @@ module ActionController #:nodoc: # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. # Defaults to <tt>File.basename(path)</tt>. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json # * <tt>:length</tt> - used to manually override the length (in bytes) of the content that # is going to be sent to the client. Defaults to <tt>File.size(path)</tt>. # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. @@ -107,7 +108,8 @@ module ActionController #:nodoc: # # Options: # * <tt>:filename</tt> - suggests a filename for the browser to use. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. # Valid values are 'inline' and 'attachment' (default). # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. @@ -143,9 +145,16 @@ module ActionController #:nodoc: disposition <<= %(; filename="#{options[:filename]}") if options[:filename] + content_type = options[:type] + if content_type.is_a?(Symbol) + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless Mime::EXTENSION_LOOKUP.has_key?(content_type.to_s) + content_type = Mime::Type.lookup_by_extension(content_type.to_s) + end + content_type = content_type.to_s.strip # fixes a problem with extra '\r' with some browsers + headers.update( 'Content-Length' => options[:length], - 'Content-Type' => options[:type].to_s.strip, # fixes a problem with extra '\r' with some browsers + 'Content-Type' => content_type, 'Content-Disposition' => disposition, 'Content-Transfer-Encoding' => 'binary' ) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 79a8e1364d..7ed1a3e160 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -93,10 +93,7 @@ module ActionController # and cookies, though. For sessions, you just do: # # @request.session[:key] = "value" - # - # For cookies, you need to manually create the cookie, like this: - # - # @request.cookies["key"] = CGI::Cookie.new("key", "value") + # @request.cookies["key"] = "value" # # == Testing named routes # diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index c4d7d52951..285a8b09e4 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -27,20 +27,19 @@ module ActionController #:nodoc: alias_method_chain :process, :test end - class TestRequest < AbstractRequest #:nodoc: + class TestRequest < Request #:nodoc: attr_accessor :cookies, :session_options - attr_accessor :query_parameters, :request_parameters, :path, :session - attr_accessor :host, :user_agent + attr_accessor :query_parameters, :path, :session + attr_accessor :host - def initialize(query_parameters = nil, request_parameters = nil, session = nil) - @query_parameters = query_parameters || {} - @request_parameters = request_parameters || {} - @session = session || TestSession.new + def initialize + super(Rack::MockRequest.env_for("/")) - initialize_containers - initialize_default_values + @query_parameters = {} + @session = TestSession.new - super() + initialize_default_values + initialize_containers end def reset_session @@ -55,7 +54,11 @@ module ActionController #:nodoc: # Either the RAW_POST_DATA environment variable or the URL-encoded request # parameters. def raw_post - env['RAW_POST_DATA'] ||= returning(url_encoded_request_parameters) { |b| b.force_encoding(Encoding::BINARY) if b.respond_to?(:force_encoding) } + @env['RAW_POST_DATA'] ||= begin + data = url_encoded_request_parameters + data.force_encoding(Encoding::BINARY) if data.respond_to?(:force_encoding) + data + end end def port=(number) @@ -125,26 +128,30 @@ module ActionController #:nodoc: path_parameters[key.to_s] = value end end + raw_post # populate env['RAW_POST_DATA'] @parameters = nil # reset TestRequest#parameters to use the new path_parameters end def recycle! - self.request_parameters = {} self.query_parameters = {} self.path_parameters = {} unmemoize_all end + def user_agent=(user_agent) + @env['HTTP_USER_AGENT'] = user_agent + end + private def initialize_containers - @env, @cookies = {}, {} + @cookies = {} end def initialize_default_values @host = "test.host" @request_uri = "/" - @user_agent = "Rails Testing" - self.remote_addr = "0.0.0.0" + @env['HTTP_USER_AGENT'] = "Rails Testing" + @env['REMOTE_ADDR'] = "0.0.0.0" @env["SERVER_PORT"] = 80 @env['REQUEST_METHOD'] = "GET" end @@ -260,14 +267,14 @@ module ActionController #:nodoc: !template_objects[name].nil? end - # Returns the response cookies, converted to a Hash of (name => CGI::Cookie) pairs + # Returns the response cookies, converted to a Hash of (name => value) pairs # - # assert_equal ['AuthorOfNewPage'], r.cookies['author'].value + # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} Array(headers['Set-Cookie']).each do |cookie| key, value = cookie.split(";").first.split("=") - cookies[key] = [value].compact + cookies[key] = value end cookies end @@ -377,20 +384,33 @@ module ActionController #:nodoc: module TestProcess def self.included(base) - # execute the request simulating a specific HTTP method and set/volley the response - # TODO: this should be un-DRY'ed for the sake of API documentation. - %w( get post put delete head ).each do |method| - base.class_eval <<-EOV, __FILE__, __LINE__ - def #{method}(action, parameters = nil, session = nil, flash = nil) - @request.env['REQUEST_METHOD'] = "#{method.upcase}" if defined?(@request) - process(action, parameters, session, flash) - end - EOV + # Executes a request simulating GET HTTP method and set/volley the response + def get(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "GET") + end + + # Executes a request simulating POST HTTP method and set/volley the response + def post(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "POST") + end + + # Executes a request simulating PUT HTTP method and set/volley the response + def put(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "PUT") + end + + # Executes a request simulating DELETE HTTP method and set/volley the response + def delete(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "DELETE") + end + + # Executes a request simulating HEAD HTTP method and set/volley the response + def head(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "HEAD") end end - # execute the request and set/volley the response - def process(action, parameters = nil, session = nil, flash = nil) + def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') # Sanity check for required instance variables so we can give an # understandable error message. %w(@controller @request @response).each do |iv_name| @@ -403,7 +423,7 @@ module ActionController #:nodoc: @response.recycle! @html_document = nil - @request.env['REQUEST_METHOD'] ||= "GET" + @request.env['REQUEST_METHOD'] = http_method @request.action = action.to_s diff --git a/actionpack/lib/action_controller/uploaded_file.rb b/actionpack/lib/action_controller/uploaded_file.rb new file mode 100644 index 0000000000..ea4845c68f --- /dev/null +++ b/actionpack/lib/action_controller/uploaded_file.rb @@ -0,0 +1,37 @@ +module ActionController + module UploadedFile + def self.included(base) + base.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 + # those which give the lone filename. + # The Windows regexp is adapted from Perl's File::Basename. + def original_filename + unless defined? @original_filename + @original_filename = + unless original_path.blank? + if original_path =~ /^(?:.*[:\\\/])?(.*)/m + $1 + else + File.basename original_path + end + end + end + @original_filename + end + end + + class UploadedStringIO < StringIO + include UploadedFile + end + + class UploadedTempfile < Tempfile + include UploadedFile + end +end diff --git a/actionpack/lib/action_controller/url_encoded_pair_parser.rb b/actionpack/lib/action_controller/url_encoded_pair_parser.rb new file mode 100644 index 0000000000..bea96c711d --- /dev/null +++ b/actionpack/lib/action_controller/url_encoded_pair_parser.rb @@ -0,0 +1,95 @@ +module ActionController + class UrlEncodedPairParser < StringScanner #:nodoc: + attr_reader :top, :parent, :result + + def initialize(pairs = []) + super('') + @result = {} + pairs.each { |key, value| parse(key, value) } + end + + KEY_REGEXP = %r{([^\[\]=&]+)} + BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} + + # Parse the query string + def parse(key, value) + self.string = key + @top, @parent = result, nil + + # First scan the bare key + key = scan(KEY_REGEXP) or return + key = post_key_check(key) + + # Then scan as many nestings as present + until eos? + r = scan(BRACKETED_KEY_REGEXP) or return + key = self[1] + key = post_key_check(key) + end + + bind(key, value) + end + + private + # After we see a key, we must look ahead to determine our next action. Cases: + # + # [] follows the key. Then the value must be an array. + # = follows the key. (A value comes next) + # & or the end of string follows the key. Then the key is a flag. + # otherwise, a hash follows the key. + def post_key_check(key) + if scan(/\[\]/) # a[b][] indicates that b is an array + container(key, Array) + nil + elsif check(/\[[^\]]/) # a[b] indicates that a is a hash + container(key, Hash) + nil + else # End of key? We do nothing. + key + end + end + + # Add a container to the stack. + def container(key, klass) + type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) + value = bind(key, klass.new) + type_conflict! klass, value unless value.is_a?(klass) + push(value) + end + + # Push a value onto the 'stack', which is actually only the top 2 items. + def push(value) + @parent, @top = @top, value + end + + # Bind a key (which may be nil for items in an array) to the provided value. + def bind(key, value) + if top.is_a? Array + if key + if top[-1].is_a?(Hash) && ! top[-1].key?(key) + top[-1][key] = value + else + top << {key => value}.with_indifferent_access + push top.last + value = top[key] + end + else + top << value + end + elsif top.is_a? Hash + key = CGI.unescape(key) + parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) + top[key] ||= value + return top[key] + else + raise ArgumentError, "Don't know what to do: top is #{top.inspect}" + end + + return value + end + + def type_conflict!(klass, value) + raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value. (The parameters received were #{value.inspect}.)" + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_controller/verb_piggybacking.rb b/actionpack/lib/action_controller/verb_piggybacking.rb new file mode 100644 index 0000000000..86cde304a0 --- /dev/null +++ b/actionpack/lib/action_controller/verb_piggybacking.rb @@ -0,0 +1,24 @@ +module ActionController + # TODO: Use Rack::MethodOverride when it is released + class VerbPiggybacking + HTTP_METHODS = %w(GET HEAD PUT POST DELETE OPTIONS) + + def initialize(app) + @app = app + end + + def call(env) + if env["REQUEST_METHOD"] == "POST" + req = Request.new(env) + if method = (req.parameters[:_method] || env["HTTP_X_HTTP_METHOD_OVERRIDE"]) + method = method.to_s.upcase + if HTTP_METHODS.include?(method) + env["REQUEST_METHOD"] = method + end + end + end + + @app.call(env) + end + end +end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 33517ffb7b..8958e61e9d 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -3,7 +3,10 @@ module ActionView #:nodoc: end class MissingTemplate < ActionViewError #:nodoc: + attr_reader :path + def initialize(paths, path, template_format = nil) + @path = path full_template_path = path.include?('.') ? path : "#{path}.erb" display_paths = paths.compact.join(":") template_type = (path =~ /layouts/i) ? 'layout' : 'template' @@ -172,17 +175,6 @@ module ActionView #:nodoc: delegate :logger, :to => 'ActionController::Base' end - # Templates that are exempt from layouts - @@exempt_from_layout = Set.new([/\.rjs$/]) - - # Don't render layouts for templates with the given extensions. - def self.exempt_from_layout(*extensions) - regexps = extensions.collect do |extension| - extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ - end - @@exempt_from_layout.merge(regexps) - end - @@debug_rjs = false ## # :singleton-method: @@ -190,12 +182,6 @@ module ActionView #:nodoc: # that alert()s the caught exception (and then re-raises it). cattr_accessor :debug_rjs - @@warn_cache_misses = false - ## - # :singleton-method: - # A warning will be displayed whenever an action results in a cache miss on your view paths. - cattr_accessor :warn_cache_misses - attr_internal :request delegate :request_forgery_protection_token, :template, :params, :session, :cookies, :response, :headers, @@ -257,7 +243,8 @@ module ActionView #:nodoc: if options[:layout] _render_with_layout(options, local_assigns, &block) elsif options[:file] - _pick_template(options[:file]).render_template(self, options[:locals]) + tempalte = self.view_paths.find_template(options[:file], template_format) + tempalte.render_template(self, options[:locals]) elsif options[:partial] render_partial(options) elsif options[:inline] @@ -315,45 +302,6 @@ module ActionView #:nodoc: end end - 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] - else - template_file_name = path - end - - # OPTIMIZE: Checks to lookup template in view path - if template = self.view_paths.find_template(template_file_name, template_format) - template - elsif (first_render = @_render_stack.first) && first_render.respond_to?(:format_and_extension) && - (template = self.view_paths["#{template_file_name}.#{first_render.format_and_extension}"]) - template - else - template = Template.new(template_path, view_paths) - - if self.class.warn_cache_misses && logger - logger.debug "[PERFORMANCE] Rendering a template that was " + - "not found in view path. Templates outside the view path are " + - "not cached and result in expensive disk operations. Move this " + - "file into #{view_paths.join(':')} or add the folder to your " + - "view path list" - end - - template - end - end - memoize :_pick_template - - def _exempt_from_layout?(template_path) #:nodoc: - template = _pick_template(template_path).to_s - @@exempt_from_layout.any? { |ext| template =~ ext } - rescue ActionView::MissingTemplate - return false - end - def _render_with_layout(options, local_assigns, &block) #:nodoc: partial_layout = options.delete(:layout) diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index a04bb8c598..4305617ac8 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -136,6 +136,10 @@ module ActionView # 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. + # * <tt>:prompt</tt> - Set to true (for a generic prompt), a prompt string or a hash of prompt strings + # for <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt> and <tt>:second</tt>. + # Setting this option prepends a select option with a generic prompt (Day, Month, Year, Hour, Minute, Seconds) + # or the given prompt string. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # @@ -171,6 +175,9 @@ module ActionView # # that will have a default day of 20. # date_select("credit_card", "bill_due", :default => { :day => 20 }) # + # # Generates a date select with custom prompts + # date_select("post", "written_on", :prompt => { :day => 'Select day', :month => 'Select month', :year => 'Select year' }) + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that @@ -210,6 +217,11 @@ module ActionView # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45. # time_select 'game', 'game_time', {:minute_step => 15} # + # # Creates a time select tag with a custom prompt. Use :prompt => true for generic prompts. + # time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'}) + # time_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours + # time_select("post", "written_on", :prompt => true) # generic prompts for all + # # The selects are prepared for multi-parameter assignment to an Active Record object. # # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that @@ -241,6 +253,11 @@ module ActionView # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) # + # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts. + # datetime_select("post", "written_on", :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # datetime_select("post", "written_on", :prompt => {:hour => true}) # generic prompt for hours + # datetime_select("post", "written_on", :prompt => true) # generic prompts for all + # # The selects are prepared for multi-parameter assignment to an Active Record object. def datetime_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_datetime_select_tag(options, html_options) @@ -285,6 +302,11 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_datetime(my_date_time, :prefix => 'payday') # + # # Generates a datetime select with a custom prompt. Use :prompt=>true for generic prompts. + # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours + # select_datetime(my_date_time, :prompt => true) # generic prompts for all + # def select_datetime(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_datetime end @@ -321,6 +343,11 @@ module ActionView # # prefixed with 'payday' rather than 'date' # select_date(my_date, :prefix => 'payday') # + # # Generates a date select with a custom prompt. Use :prompt=>true for generic prompts. + # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours + # select_date(my_date, :prompt => true) # generic prompts for all + # def select_date(date = Date.current, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_date end @@ -352,6 +379,11 @@ module ActionView # # separated by ':' and includes an input for seconds # select_time(my_time, :time_separator => ':', :include_seconds => true) # + # # Generates a time select with a custom prompt. Use :prompt=>true for generic prompts. + # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) + # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours + # select_time(my_time, :prompt => true) # generic prompts for all + # def select_time(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_time end @@ -373,6 +405,10 @@ module ActionView # # that is named 'interval' rather than 'second' # select_second(my_time, :field_name => 'interval') # + # # Generates a select field for seconds with a custom prompt. Use :prompt=>true for a + # # generic prompt. + # select_minute(14, :prompt => 'Choose seconds') + # def select_second(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_second end @@ -395,6 +431,10 @@ module ActionView # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # + # # Generates a select field for minutes with a custom prompt. Use :prompt=>true for a + # # generic prompt. + # select_minute(14, :prompt => 'Choose minutes') + # def select_minute(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_minute end @@ -416,6 +456,10 @@ module ActionView # # that is named 'stride' rather than 'second' # select_hour(my_time, :field_name => 'stride') # + # # Generates a select field for hours with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_hour(13, :prompt =>'Choose hour') + # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end @@ -437,6 +481,10 @@ module ActionView # # that is named 'due' rather than 'day' # select_day(my_time, :field_name => 'due') # + # # Generates a select field for days with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_day(5, :prompt => 'Choose day') + # def select_day(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_day end @@ -475,6 +523,10 @@ module ActionView # # will use keys like "Januar", "Marts." # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # + # # Generates a select field for months with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_month(14, :prompt => 'Choose month') + # def select_month(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_month end @@ -502,6 +554,10 @@ module ActionView # # has ascending year values # select_year(2006, :start_year => 2000, :end_year => 2010) # + # # Generates a select field for years with a custom prompt. Use :prompt => true for a + # # generic prompt. + # select_year(14, :prompt => 'Choose year') + # def select_year(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_year end @@ -764,11 +820,30 @@ module ActionView select_html = "\n" select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] + select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html.to_s content_tag(:select, select_html, select_options) + "\n" end + # Builds a prompt option tag with supplied options or from default options + # prompt_option_tag(:month, :prompt => 'Select month') + # => "<option value="">Select month</option>" + def prompt_option_tag(type, options) + default_options = {:year => false, :month => false, :day => false, :hour => false, :minute => false, :second => false} + + case options + when Hash + prompt = default_options.merge(options)[type.to_sym] + when String + prompt = options + else + prompt = I18n.translate(('datetime.prompts.' + type.to_s).to_sym, :locale => @options[:locale]) + end + + prompt ? content_tag(:option, prompt, :value => '') : '' + end + # 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" />" diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 621e2946b5..a85751c657 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -737,9 +737,13 @@ module ActionView (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| src = <<-end_src - def #{selector}(method, options = {}) - @template.send(#{selector.inspect}, @object_name, method, objectify_options(options)) - end + def #{selector}(method, options = {}) # def text_field(method, options = {}) + @template.send( # @template.send( + #{selector.inspect}, # "text_field", + @object_name, # @object_name, + method, # method, + objectify_options(options)) # objectify_options(options)) + end # end end_src class_eval src, __FILE__, __LINE__ end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 7fab3102e7..18a209dcea 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -531,11 +531,6 @@ module ActionView # is shorthand for # :with => "'name=' + value" # This essentially just changes the key of the parameter. - # <tt>:on</tt>:: Specifies which event handler to observe. By default, - # it's set to "changed" for text fields and areas and - # "click" for radio buttons and checkboxes. With this, - # you can specify it instead to be "blur" or "focus" or - # any other event. # # Additionally, you may specify any of the options documented in the # <em>Common options</em> section at the top of this document. @@ -548,11 +543,6 @@ module ActionView # :url => 'http://example.com/books/edit/1', # :with => 'title' # - # # Sends params: {:book_title => 'Title of the book'} when the focus leaves - # # the input field. - # observe_field 'book_title', - # :url => 'http://example.com/books/edit/1', - # :on => 'blur' # def observe_field(field_id, options = {}) if options[:frequency] && options[:frequency] > 0 @@ -1094,7 +1084,6 @@ module ActionView javascript << "#{options[:frequency]}, " if options[:frequency] javascript << "function(element, value) {" javascript << "#{callback}}" - javascript << ", '#{options[:on]}'" if options[:on] javascript << ")" javascript_tag(javascript) end diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 9542b035aa..a880fd83ef 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -80,6 +80,13 @@ over_x_years: one: "over 1 year" other: "over {{count}} years" + prompts: + year: "Year" + month: "Month" + day: "Day" + hour: "Hour" + minute: "Minute" + second: "Seconds" activerecord: errors: diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index bbc995a340..59e82b98a4 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -228,7 +228,7 @@ module ActionView path = "_#{partial_path}" end - _pick_template(path) + self.view_paths.find_template(path, self.template_format) end memoize :_pick_partial_template end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 623b9ff6b0..b030156889 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -2,13 +2,6 @@ module ActionView #:nodoc: class PathSet < Array #:nodoc: def self.type_cast(obj) if obj.is_a?(String) - if Base.warn_cache_misses && defined?(Rails) && Rails.initialized? - Base.logger.debug "[PERFORMANCE] Processing view path during a " + - "request. This an expense disk operation that should be done at " + - "boot. You can manually process this view path with " + - "ActionView::Base.process_view_paths(#{obj.inspect}) and set it " + - "as your view path" - end Path.new(obj) else obj @@ -92,7 +85,7 @@ module ActionView #:nodoc: else Dir.glob("#{@path}/#{path}*").each do |file| template = create_template(file) - if path == template.path_without_extension || path == template.path + if template.accessible_paths.include?(path) return template end end @@ -115,8 +108,9 @@ module ActionView #:nodoc: templates_in_path do |template| template.load! - @paths[template.path] = template - @paths[template.path_without_extension] ||= template + template.accessible_paths.each do |path| + @paths[path] = template + end end @paths.freeze @@ -143,28 +137,19 @@ module ActionView #:nodoc: each { |path| path.reload! } end - def [](template_path) - each do |path| - if template = path[template_path] - return template - end - end - nil - end + def find_template(original_template_path, format = nil) + return original_template_path if original_template_path.respond_to?(:render) + template_path = original_template_path.sub(/^\//, '') - def find_template(path, *formats) - if formats && formats.first == :all - formats = Mime::EXTENSION_LOOKUP.values.map(&:to_sym) - end - formats.each do |format| - if template = self["#{path}.#{format}"] + each do |load_path| + if format && (template = load_path["#{template_path}.#{format}"]) + return template + elsif template = load_path[template_path] return template end end - if template = self[path] - return template - end - nil + + Template.new(original_template_path, self) end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 7c0e62f1d7..d8e72f1179 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -4,10 +4,6 @@ module ActionView module Renderable #:nodoc: extend ActiveSupport::Memoizable - def self.included(base) - @@mutex = Mutex.new - end - def filename 'compiled-template' end @@ -22,6 +18,11 @@ module ActionView end memoize :compiled_source + def method_name_without_locals + ['_run', extension, method_segment].compact.join('_') + end + memoize :method_name_without_locals + def render(view, local_assigns = {}) compile(local_assigns) @@ -46,9 +47,12 @@ module ActionView 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('_')}" + method_name = method_name_without_locals.dup + method_name << "_locals_#{local_assigns.keys.map { |k| k.to_s }.sort.join('_')}" + else + method_name = method_name_without_locals end - ['_run', extension, method_segment, local_assigns_keys].compact.join('_').to_sym + method_name.to_sym end private @@ -56,10 +60,8 @@ module ActionView def compile(local_assigns) render_symbol = method_name(local_assigns) - @@mutex.synchronize do - if recompile?(render_symbol) - compile!(render_symbol, local_assigns) - end + if recompile?(render_symbol) + compile!(render_symbol, local_assigns) end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 93748638c3..5b384d0e4d 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -4,6 +4,17 @@ module ActionView #:nodoc: extend ActiveSupport::Memoizable include Renderable + # Templates that are exempt from layouts + @@exempt_from_layout = Set.new([/\.rjs$/]) + + # Don't render layouts for templates with the given extensions. + def self.exempt_from_layout(*extensions) + regexps = extensions.collect do |extension| + extension.is_a?(Regexp) ? extension : /\.#{Regexp.escape(extension.to_s)}$/ + end + @@exempt_from_layout.merge(regexps) + end + attr_accessor :filename, :load_path, :base_path, :name, :format, :extension delegate :to_s, :to => :path @@ -17,6 +28,18 @@ module ActionView #:nodoc: extend RenderablePartial if @name =~ /^_/ end + def accessible_paths + paths = [] + paths << path + paths << path_without_extension + if multipart? + formats = format.split(".") + paths << "#{path_without_format_and_extension}.#{formats.first}" + paths << "#{path_without_format_and_extension}.#{formats.second}" + end + paths + end + def format_and_extension (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions end @@ -57,6 +80,10 @@ module ActionView #:nodoc: end memoize :relative_path + def exempt_from_layout? + @@exempt_from_layout.any? { |exempted| path =~ exempted } + end + def mtime File.mtime(filename) end @@ -94,6 +121,7 @@ module ActionView #:nodoc: def load! @loaded = true + compile({}) freeze end diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index ed8c4427c9..99c57c0c91 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -248,6 +248,14 @@ class AssertSelectTest < ActionController::TestCase end end + def test_assert_select_rjs_for_positioned_insert_should_fail_when_mixing_arguments + render_rjs do |page| + page.insert_html :top, "test1", "<div id=\"1\">foo</div>" + page.insert_html :bottom, "test2", "<div id=\"2\">foo</div>" + end + assert_raises(Assertion) {assert_select_rjs :insert, :top, "test2"} + end + # # Test css_select. # diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index e24bb00bc7..7f8e47ba58 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -121,8 +121,7 @@ class PageCachingTest < ActionController::TestCase [:get, :post, :put, :delete].each do |method| unless method == :get and status == :ok define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do - @request.env['REQUEST_METHOD'] = method.to_s.upcase - process status + send(method, status) assert_response status assert_page_not_cached status, "#{method} with #{status} status shouldn't have been cached" end diff --git a/actionpack/test/controller/cookie_test.rb b/actionpack/test/controller/cookie_test.rb index 4b969519c6..3ddc5768a9 100644 --- a/actionpack/test/controller/cookie_test.rb +++ b/actionpack/test/controller/cookie_test.rb @@ -7,15 +7,15 @@ class CookieTest < Test::Unit::TestCase end def authenticate_for_fourteen_days - cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) } + cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) } end def authenticate_for_fourteen_days_with_symbols - cookies[:user_name] = { :value => "david", :expires => Time.local(2005, 10, 10) } + cookies[:user_name] = { :value => "david", :expires => Time.utc(2005, 10, 10,5) } end def set_multiple_cookies - cookies["user_name"] = { "value" => "david", "expires" => Time.local(2005, 10, 10) } + cookies["user_name"] = { "value" => "david", "expires" => Time.utc(2005, 10, 10,5) } cookies["login"] = "XJ-122" end @@ -52,33 +52,33 @@ class CookieTest < Test::Unit::TestCase def test_setting_cookie get :authenticate assert_equal ["user_name=david; path=/"], @response.headers["Set-Cookie"] - assert_equal({"user_name" => ["david"]}, @response.cookies) + assert_equal({"user_name" => "david"}, @response.cookies) end def test_setting_cookie_for_fourteen_days get :authenticate_for_fourteen_days - assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"] - assert_equal({"user_name" => ["david"]}, @response.cookies) + assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => "david"}, @response.cookies) end def test_setting_cookie_for_fourteen_days_with_symbols get :authenticate_for_fourteen_days_with_symbols - assert_equal ["user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT"], @response.headers["Set-Cookie"] - assert_equal({"user_name" => ["david"]}, @response.cookies) + assert_equal ["user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => "david"}, @response.cookies) end def test_setting_cookie_with_http_only get :authenticate_with_http_only assert_equal ["user_name=david; path=/; HttpOnly"], @response.headers["Set-Cookie"] - assert_equal({"user_name" => ["david"]}, @response.cookies) + assert_equal({"user_name" => "david"}, @response.cookies) end def test_multiple_cookies get :set_multiple_cookies assert_equal 2, @response.cookies.size - assert_equal "user_name=david; path=/; expires=Mon, 10 Oct 2005 05:00:00 GMT", @response.headers["Set-Cookie"][0] + assert_equal "user_name=david; path=/; expires=Mon, 10-Oct-2005 05:00:00 GMT", @response.headers["Set-Cookie"][0] assert_equal "login=XJ-122; path=/", @response.headers["Set-Cookie"][1] - assert_equal({"login" => ["XJ-122"], "user_name" => ["david"]}, @response.cookies) + assert_equal({"login" => "XJ-122", "user_name" => "david"}, @response.cookies) end def test_setting_test_cookie @@ -87,12 +87,12 @@ class CookieTest < Test::Unit::TestCase def test_expiring_cookie get :logout - assert_equal ["user_name=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"] - assert_equal({"user_name" => []}, @response.cookies) + assert_equal ["user_name=; path=/; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"] + assert_equal({"user_name" => nil}, @response.cookies) end def test_cookiejar_accessor - @request.cookies["user_name"] = CGI::Cookie.new("name" => "user_name", "value" => "david", "expires" => Time.local(2025, 10, 10)) + @request.cookies["user_name"] = "david" @controller.request = @request jar = ActionController::CookieJar.new(@controller) assert_equal "david", jar["user_name"] @@ -100,52 +100,14 @@ class CookieTest < Test::Unit::TestCase end def test_cookiejar_accessor_with_array_value - a = %w{1 2 3} - @request.cookies["pages"] = CGI::Cookie.new("name" => "pages", "value" => a, "expires" => Time.local(2025, 10, 10)) + @request.cookies["pages"] = %w{1 2 3} @controller.request = @request jar = ActionController::CookieJar.new(@controller) - assert_equal a, jar["pages"] + assert_equal %w{1 2 3}, jar["pages"] end def test_delete_cookie_with_path get :delete_cookie_with_path - assert_equal ["user_name=; path=/beaten; expires=Thu, 01 Jan 1970 00:00:00 GMT"], @response.headers["Set-Cookie"] - end - - def test_cookie_to_s_simple_values - assert_equal 'myname=myvalue; path=', CGI::Cookie.new('myname', 'myvalue').to_s - end - - def test_cookie_to_s_hash - cookie_str = CGI::Cookie.new( - 'name' => 'myname', - 'value' => 'myvalue', - 'domain' => 'mydomain', - 'path' => 'mypath', - 'expires' => Time.utc(2007, 10, 20), - 'secure' => true, - 'http_only' => true).to_s - assert_equal 'myname=myvalue; domain=mydomain; path=mypath; expires=Sat, 20 Oct 2007 00:00:00 GMT; secure; HttpOnly', cookie_str - end - - def test_cookie_to_s_hash_default_not_secure_not_http_only - cookie_str = CGI::Cookie.new( - 'name' => 'myname', - 'value' => 'myvalue', - 'domain' => 'mydomain', - 'path' => 'mypath', - 'expires' => Time.utc(2007, 10, 20)) - assert cookie_str !~ /secure/ - assert cookie_str !~ /HttpOnly/ - end - - def test_cookies_should_not_be_split_on_ampersand_values - cookies = CGI::Cookie.parse('return_to=http://rubyonrails.org/search?term=api&scope=all&global=true') - assert_equal({"return_to" => ["http://rubyonrails.org/search?term=api&scope=all&global=true"]}, cookies) - end - - def test_cookies_should_not_be_split_on_values_with_newlines - cookies = CGI::Cookie.new("name" => "val", "value" => "this\nis\na\ntest") - assert cookies.size == 1 + assert_equal ["user_name=; path=/beaten; expires=Thu, 01-Jan-1970 00:00:00 GMT"], @response.headers["Set-Cookie"] end end diff --git a/actionpack/test/controller/dispatcher_test.rb b/actionpack/test/controller/dispatcher_test.rb index fd06b4ea99..da87d26146 100644 --- a/actionpack/test/controller/dispatcher_test.rb +++ b/actionpack/test/controller/dispatcher_test.rb @@ -96,9 +96,7 @@ class DispatcherTest < Test::Unit::TestCase private def dispatch(cache_classes = true) - controller = mock() - controller.stubs(:process).returns([200, {}, 'response']) - ActionController::Routing::Routes.stubs(:recognize).returns(controller) + ActionController::Routing::RouteSet.any_instance.stubs(:call).returns([200, {}, 'response']) Dispatcher.define_dispatcher_callbacks(cache_classes) @dispatcher.call({}) end diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb new file mode 100644 index 0000000000..d5c8636a9e --- /dev/null +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -0,0 +1,73 @@ +require 'abstract_unit' + +class HttpDigestAuthenticationTest < Test::Unit::TestCase + include ActionController::HttpAuthentication::Digest + + class DummyController + attr_accessor :headers, :renders, :request, :response + + def initialize + @headers, @renders = {}, [] + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + request.session.session_id = "test_session" + end + + def render(options) + self.renderers << options + end + end + + def setup + @controller = DummyController.new + @credentials = { + :username => "dhh", + :realm => "testrealm@host.com", + :nonce => ActionController::HttpAuthentication::Digest.nonce(@controller.request), + :qop => "auth", + :nc => "00000001", + :cnonce => "0a4f113b", + :opaque => ActionController::HttpAuthentication::Digest.opaque(@controller.request), + :uri => "http://test.host/" + } + @encoded_credentials = ActionController::HttpAuthentication::Digest.encode_credentials("GET", @credentials, "secret") + end + + def test_decode_credentials + set_headers + assert_equal @credentials, decode_credentials(@controller.request) + end + + def test_nonce_format + assert_nothing_thrown do + validate_nonce(@controller.request, nonce(@controller.request)) + end + end + + def test_authenticate_should_raise_for_nil_password + set_headers ActionController::HttpAuthentication::Digest.encode_credentials(:get, @credentials, nil) + assert_raise ActionController::HttpAuthentication::Error do + authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" } + end + end + + def test_authenticate_should_raise_for_incorrect_password + set_headers + assert_raise ActionController::HttpAuthentication::Error do + authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "bad password" } + end + end + + def test_authenticate_should_not_raise_for_correct_password + set_headers + assert_nothing_thrown do + authenticate(@controller, @credentials[:realm]) { |user| user == "dhh" && "secret" } + end + end + + private + def set_headers(value = @encoded_credentials, name = 'HTTP_AUTHORIZATION', method = "GET") + @controller.request.env[name] = value + @controller.request.env["REQUEST_METHOD"] = method + end +end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index c28050fe0d..53cebf768e 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -8,7 +8,25 @@ class SessionTest < Test::Unit::TestCase } def setup + @credentials = { + :username => "username", + :realm => "MyApp", + :nonce => ActionController::HttpAuthentication::Digest.nonce("session_id"), + :qop => "auth", + :nc => "00000001", + :cnonce => "0a4f113b", + :opaque => ActionController::HttpAuthentication::Digest.opaque("session_id"), + :uri => "/index" + } + @session = ActionController::Integration::Session.new(StubApp) + @session.nonce = @credentials[:nonce] + @session.opaque = @credentials[:opaque] + @session.realm = @credentials[:realm] + end + + def encoded_credentials(method) + ActionController::HttpAuthentication::Digest.encode_credentials(method, @credentials, "password") end def test_https_bang_works_and_sets_truth_by_default @@ -132,6 +150,76 @@ class SessionTest < Test::Unit::TestCase @session.head(path,params,headers) end + def test_get_with_basic + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n") + @session.expects(:process).with(:get,path,params,expected_headers) + @session.get_with_basic(path,params,headers,'username','password') + end + + def test_post_with_basic + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n") + @session.expects(:process).with(:post,path,params,expected_headers) + @session.post_with_basic(path,params,headers,'username','password') + end + + def test_put_with_basic + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n") + @session.expects(:process).with(:put,path,params,expected_headers) + @session.put_with_basic(path,params,headers,'username','password') + end + + def test_delete_with_basic + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n") + @session.expects(:process).with(:delete,path,params,expected_headers) + @session.delete_with_basic(path,params,headers,'username','password') + end + + def test_head_with_basic + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => "Basic dXNlcm5hbWU6cGFzc3dvcmQ=\n") + @session.expects(:process).with(:head,path,params,expected_headers) + @session.head_with_basic(path,params,headers,'username','password') + end + + def test_get_with_digest + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => encoded_credentials(:get)) + @session.expects(:process).with(:get,path,params,expected_headers) + @session.get_with_digest(path,params,headers,'username','password') + end + + def test_post_with_digest + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => encoded_credentials(:post)) + @session.expects(:process).with(:post,path,params,expected_headers) + @session.post_with_digest(path,params,headers,'username','password') + end + + def test_put_with_digest + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => encoded_credentials(:put)) + @session.expects(:process).with(:put,path,params,expected_headers) + @session.put_with_digest(path,params,headers,'username','password') + end + + def test_delete_with_digest + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => encoded_credentials(:delete)) + @session.expects(:process).with(:delete,path,params,expected_headers) + @session.delete_with_digest(path,params,headers,'username','password') + end + + def test_head_with_digest + path = "/index"; params = "blah"; headers = {:location => 'blah'} + expected_headers = headers.merge(:authorization => encoded_credentials(:head)) + @session.expects(:process).with(:head,path,params,expected_headers) + @session.head_with_digest(path,params,headers,'username','password') + end + def test_xml_http_request_get path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 18c01f755c..c2efe9d00b 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -165,15 +165,17 @@ class LayoutStatusIsRenderedTest < ActionController::TestCase end end -class LayoutSymlinkedTest < LayoutTest - layout "symlinked/symlinked_layout" -end - -class LayoutSymlinkedIsRenderedTest < ActionController::TestCase - def test_symlinked_layout_is_rendered - @controller = LayoutSymlinkedTest.new - get :hello - assert_response 200 - assert_equal "layouts/symlinked/symlinked_layout", @response.layout +unless RUBY_PLATFORM =~ /(:?mswin|mingw|bccwin)/ + class LayoutSymlinkedTest < LayoutTest + layout "symlinked/symlinked_layout" + end + + class LayoutSymlinkedIsRenderedTest < ActionController::TestCase + def test_symlinked_layout_is_rendered + @controller = LayoutSymlinkedTest.new + get :hello + assert_response 200 + assert_equal "layouts/symlinked/symlinked_layout", @response.layout + end end end diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb index 81d103f0f9..31bff4ae6d 100644 --- a/actionpack/test/controller/rack_test.rb +++ b/actionpack/test/controller/rack_test.rb @@ -4,7 +4,7 @@ class BaseRackTest < Test::Unit::TestCase def setup @env = { "HTTP_MAX_FORWARDS" => "10", - "SERVER_NAME" => "glu.ttono.us:8007", + "SERVER_NAME" => "glu.ttono.us", "FCGI_ROLE" => "RESPONDER", "AUTH_TYPE" => "Basic", "HTTP_X_FORWARDED_HOST" => "glu.ttono.us", @@ -43,10 +43,10 @@ class BaseRackTest < Test::Unit::TestCase "REDIRECT_STATUS" => "200", "REQUEST_METHOD" => "GET" } - @request = ActionController::RackRequest.new(@env) + @request = ActionController::Request.new(@env) # some Nokia phone browsers omit the space after the semicolon separator. # some developers have grown accustomed to using comma in cookie values. - @alt_cookie_fmt_request = ActionController::RackRequest.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"})) + @alt_cookie_fmt_request = ActionController::Request.new(@env.merge({"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"})) end def default_test; end @@ -145,7 +145,7 @@ class RackRequestTest < BaseRackTest assert_equal "kevin", @request.remote_user assert_equal :get, @request.request_method assert_equal "/dispatch.fcgi", @request.script_name - assert_equal "glu.ttono.us:8007", @request.server_name + assert_equal "glu.ttono.us", @request.server_name assert_equal 8007, @request.server_port assert_equal "HTTP/1.1", @request.server_protocol assert_equal "lighttpd", @request.server_software @@ -187,29 +187,6 @@ class RackRequestContentTypeTest < BaseRackTest end end -class RackRequestMethodTest < BaseRackTest - def test_get - assert_equal :get, @request.request_method - end - - def test_post - @request.env['REQUEST_METHOD'] = 'POST' - assert_equal :post, @request.request_method - end - - def test_put - set_content_data '_method=put' - - assert_equal :put, @request.request_method - end - - def test_delete - set_content_data '_method=delete' - - assert_equal :delete, @request.request_method - end -end - class RackRequestNeedsRewoundTest < BaseRackTest def test_body_should_be_rewound data = 'foo' @@ -218,7 +195,7 @@ class RackRequestNeedsRewoundTest < BaseRackTest @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8' # Read the request body by parsing params. - request = ActionController::RackRequest.new(@env) + request = ActionController::Request.new(@env) request.request_parameters # Should have rewound the body. diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 8e08a5a8e9..5fd41d8eec 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -21,6 +21,8 @@ class MockLogger end class TestController < ActionController::Base + protect_from_forgery + class LabellingFormBuilder < ActionView::Helpers::FormBuilder end @@ -79,6 +81,10 @@ class TestController < ActionController::Base render :action => "hello_world" end + def render_action_hello_world_as_string + render "hello_world" + end + def render_action_hello_world_with_symbol render :action => :hello_world end @@ -102,6 +108,12 @@ class TestController < ActionController::Base render :file => path end + def render_file_as_string_with_instance_variables + @secret = 'in the sauce' + path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')) + render path + end + def render_file_not_using_full_path @secret = 'in the sauce' render :file => 'test/render_file_with_ivar' @@ -122,6 +134,11 @@ class TestController < ActionController::Base render :file => path, :locals => {:secret => 'in the sauce'} end + def render_file_as_string_with_locals + path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_locals.erb')) + render path, :locals => {:secret => 'in the sauce'} + end + def accessing_request_in_template render :inline => "Hello: <%= request.host %>" end @@ -180,10 +197,6 @@ class TestController < ActionController::Base render :text => "appended" end - def render_invalid_args - render("test/hello") - end - def render_vanilla_js_hello render :js => "alert('hello')" end @@ -193,6 +206,11 @@ class TestController < ActionController::Base render :template => "test/hello" end + def render_xml_hello_as_string_template + @name = "David" + render "test/hello" + end + def render_xml_with_custom_content_type render :xml => "<blah/>", :content_type => "application/atomsvc+xml" end @@ -282,6 +300,14 @@ class TestController < ActionController::Base render :action => "hello_world", :layout => "standard" end + def layout_test_with_different_layout_and_string_action + render "hello_world", :layout => "standard" + end + + def layout_test_with_different_layout_and_symbol_action + render :hello_world, :layout => "standard" + end + def rendering_without_layout render :action => "hello_world", :layout => false end @@ -323,6 +349,10 @@ class TestController < ActionController::Base render :template => "test/hello_world" end + def render_with_explicit_string_template + render "test/hello_world" + end + def render_with_explicit_template_with_locals render :template => "test/render_file_with_locals", :locals => { :secret => 'area51' } end @@ -645,6 +675,7 @@ class TestController < ActionController::Base "accessing_params_in_template", "accessing_params_in_template_with_layout", "render_with_explicit_template", + "render_with_explicit_string_template", "render_js_with_explicit_template", "render_js_with_explicit_action_template", "delete_with_js", "update_page", "update_page_with_instance_variables" @@ -724,6 +755,12 @@ class RenderTest < ActionController::TestCase assert_template "test/hello_world" end + def test_render_action_hello_world_as_string + get :render_action_hello_world_as_string + assert_equal "Hello world!", @response.body + assert_template "test/hello_world" + end + def test_render_action_with_symbol get :render_action_hello_world_with_symbol assert_template "test/hello_world" @@ -749,6 +786,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file_as_string_with_instance_variables + get :render_file_as_string_with_instance_variables + assert_equal "The secret is in the sauce\n", @response.body + end + def test_render_file_not_using_full_path get :render_file_not_using_full_path assert_equal "The secret is in the sauce\n", @response.body @@ -764,6 +806,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file_as_string_with_locals + get :render_file_as_string_with_locals + assert_equal "The secret is in the sauce\n", @response.body + end + def test_render_file_from_template get :render_file_from_template assert_equal "The secret is in the sauce\n", @response.body @@ -829,10 +876,6 @@ class RenderTest < ActionController::TestCase assert_equal 'appended', @response.body end - def test_attempt_to_render_with_invalid_arguments - assert_raises(ActionController::RenderError) { get :render_invalid_args } - end - def test_attempt_to_access_object_method assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } end @@ -873,6 +916,12 @@ class RenderTest < ActionController::TestCase assert_equal "application/xml", @response.content_type end + def test_render_xml_as_string_template + get :render_xml_hello_as_string_template + assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body + assert_equal "application/xml", @response.content_type + end + def test_render_xml_with_default get :greeting assert_equal "<p>This is grand!</p>\n", @response.body @@ -1012,6 +1061,16 @@ class RenderTest < ActionController::TestCase assert_equal "<html>Hello world!</html>", @response.body end + def test_layout_test_with_different_layout_and_string_action + get :layout_test_with_different_layout_and_string_action + assert_equal "<html>Hello world!</html>", @response.body + end + + def test_layout_test_with_different_layout_and_symbol_action + get :layout_test_with_different_layout_and_symbol_action + assert_equal "<html>Hello world!</html>", @response.body + end + def test_rendering_without_layout get :rendering_without_layout assert_equal "Hello world!", @response.body @@ -1058,6 +1117,11 @@ class RenderTest < ActionController::TestCase assert_response :success end + def test_render_with_explicit_string_template + get :render_with_explicit_string_template + assert_equal "<html>Hello world!</html>", @response.body + end + def test_double_render assert_raises(ActionController::DoubleRenderError) { get :double_render } end diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index 71da50fab0..349cea268f 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -303,18 +303,16 @@ class RequestTest < ActiveSupport::TestCase end def test_allow_method_hacking_on_post - self.request_method = :post [:get, :head, :options, :put, :post, :delete].each do |method| - @request.instance_eval { @parameters = { :_method => method.to_s } ; @request_method = nil } + self.request_method = method @request.request_method(true) assert_equal(method == :head ? :get : method, @request.method) end end def test_invalid_method_hacking_on_post_raises_exception - self.request_method = :post - @request.instance_eval { @parameters = { :_method => :random_method } ; @request_method = nil } assert_raises(ActionController::UnknownHttpMethod) do + self.request_method = :_random_method @request.request_method(true) end end @@ -426,95 +424,95 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase def test_query_string assert_equal( { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, - ActionController::AbstractRequest.parse_query_parameters(@query_string) + ActionController::RequestParser.parse_query_parameters(@query_string) ) end def test_deep_query_string expected = {'x' => {'y' => {'z' => '10'}}} - assert_equal(expected, ActionController::AbstractRequest.parse_query_parameters('x[y][z]=10')) + assert_equal(expected, ActionController::RequestParser.parse_query_parameters('x[y][z]=10')) end def test_deep_query_string_with_array - assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10')) - assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10&x[y][z][]=5')) + assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10')) + assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::RequestParser.parse_query_parameters('x[y][z][]=10&x[y][z][]=5')) end def test_deep_query_string_with_array_of_hash - assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10')) - assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) + assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10')) + assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) end def test_deep_query_string_with_array_of_hashes_with_one_pair - assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')) - assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) - assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z]) + assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')) + assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) + assert_equal("10", ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z]) end def test_deep_query_string_with_array_of_hashes_with_multiple_pairs assert_equal( {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, - ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') + ActionController::RequestParser.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') ) end def test_query_string_with_nil assert_equal( { "action" => "create_customer", "full_name" => ''}, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty) + ActionController::RequestParser.parse_query_parameters(@query_string_with_empty) ) end def test_query_string_with_array assert_equal( { "action" => "create_customer", "selected" => ["1", "2", "3"]}, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_array) + ActionController::RequestParser.parse_query_parameters(@query_string_with_array) ) end def test_query_string_with_amps assert_equal( { "action" => "create_customer", "name" => "Don't & Does"}, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_amps) + ActionController::RequestParser.parse_query_parameters(@query_string_with_amps) ) end def test_query_string_with_many_equal assert_equal( { "action" => "create_customer", "full_name" => "abc=def=ghi"}, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_equal) + ActionController::RequestParser.parse_query_parameters(@query_string_with_many_equal) ) end def test_query_string_without_equal assert_equal( { "action" => nil }, - ActionController::AbstractRequest.parse_query_parameters(@query_string_without_equal) + ActionController::RequestParser.parse_query_parameters(@query_string_without_equal) ) end def test_query_string_with_empty_key assert_equal( { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty_key) + ActionController::RequestParser.parse_query_parameters(@query_string_with_empty_key) ) end def test_query_string_with_many_ampersands assert_equal( { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, - ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_ampersands) + ActionController::RequestParser.parse_query_parameters(@query_string_with_many_ampersands) ) end def test_unbalanced_query_string_with_array assert_equal( {'location' => ["1", "2"], 'age_group' => ["2"]}, - ActionController::AbstractRequest.parse_query_parameters("location[]=1&location[]=2&age_group[]=2") + ActionController::RequestParser.parse_query_parameters("location[]=1&location[]=2&age_group[]=2") ) assert_equal( {'location' => ["1", "2"], 'age_group' => ["2"]}, - ActionController::AbstractRequest.parse_request_parameters({'location[]' => ["1", "2"], + ActionController::RequestParser.parse_request_parameters({'location[]' => ["1", "2"], 'age_group[]' => ["2"]}) ) end @@ -527,7 +525,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } - assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query)) + assert_equal(expected, ActionController::RequestParser.parse_request_parameters(query)) end def test_parse_params @@ -566,7 +564,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase } } - assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input) end UploadedStringIO = ActionController::UploadedStringIO @@ -621,7 +619,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase "text_part" => "abc" } - params = ActionController::AbstractRequest.parse_request_parameters(input) + params = ActionController::RequestParser.parse_request_parameters(input) assert_equal expected_output, params # Lone filenames are preserved. @@ -652,7 +650,7 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase "logo" => File.new(File.dirname(__FILE__) + "/rack_test.rb").path, } - assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_array @@ -660,55 +658,55 @@ class UrlEncodedRequestParameterParsingTest < ActiveSupport::TestCase expected_output = { "selected" => [ "1", "2", "3" ] } - assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected_output, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_non_alphanumeric_name input = { "a/b[c]" => %w(d) } expected = { "a/b" => { "c" => "d" }} - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_single_brackets_in_middle input = { "a/b[c]d" => %w(e) } expected = { "a/b" => {} } - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_separated_brackets input = { "a/b@[c]d[e]" => %w(f) } expected = { "a/b@" => { }} - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_separated_brackets_and_array input = { "a/b@[c]d[e][]" => %w(f) } expected = { "a/b@" => { }} - assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected , ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_unmatched_brackets_and_array input = { "a/b@[c][d[e][]" => %w(f) } expected = { "a/b@" => { "c" => { }}} - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_nil_key input = { nil => nil, "test2" => %w(value1) } expected = { "test2" => "value1" } - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_array_prefix_and_hashes input = { "a[][b][c]" => %w(d) } expected = {"a" => [{"b" => {"c" => "d"}}]} - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end def test_parse_params_with_complex_nesting input = { "a[][b][c][][d][]" => %w(e) } expected = {"a" => [{"b" => {"c" => [{"d" => ["e"]}]}}]} - assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) + assert_equal expected, ActionController::RequestParser.parse_request_parameters(input) end end @@ -770,7 +768,7 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase # Ensures that parse_multipart_form_parameters works with streams that cannot be rewound file = File.open(File.join(FIXTURE_PATH, 'large_text_file'), 'rb') file.expects(:rewind).raises(Errno::ESPIPE) - params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {}) + params = ActionController::RequestParser.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {}) assert_not_equal 0, file.pos # file was not rewound after reading end end @@ -809,7 +807,7 @@ class MultipartRequestParameterParsingTest < ActiveSupport::TestCase private def parse_multipart(name) File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| - params = ActionController::AbstractRequest.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {}) + params = ActionController::RequestParser.parse_multipart_form_parameters(file, 'AaB03x', file.stat.size, {}) assert_equal 0, file.pos # file was rewound after reading params end @@ -856,7 +854,7 @@ class XmlParamsParsingTest < ActiveSupport::TestCase env = { 'rack.input' => StringIO.new(body), 'CONTENT_TYPE' => 'application/xml', 'CONTENT_LENGTH' => body.size.to_s } - ActionController::RackRequest.new(env).request_parameters + ActionController::Request.new(env).request_parameters end end @@ -866,7 +864,7 @@ class LegacyXmlParamsParsingTest < XmlParamsParsingTest env = { 'rack.input' => StringIO.new(body), 'HTTP_X_POST_DATA_FORMAT' => 'xml', 'CONTENT_LENGTH' => body.size.to_s } - ActionController::RackRequest.new(env).request_parameters + ActionController::Request.new(env).request_parameters end end @@ -888,6 +886,6 @@ class JsonParamsParsingTest < ActiveSupport::TestCase env = { 'rack.input' => StringIO.new(body), 'CONTENT_TYPE' => content_type, 'CONTENT_LENGTH' => body.size.to_s } - ActionController::RackRequest.new(env).request_parameters + ActionController::Request.new(env).request_parameters end end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 63f9827f4a..49aca3a6ee 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -367,7 +367,11 @@ class RescueControllerTest < ActionController::TestCase end def test_rescue_dispatcher_exceptions - RescueController.process_with_exception(@request, @response, ActionController::RoutingError.new("Route not found")) + env = @request.env + env["actioncontroller.rescue.request"] = @request + env["actioncontroller.rescue.response"] = @response + + RescueController.call_with_exception(env, ActionController::RoutingError.new("Route not found")) assert_equal "no way", @response.body end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index d5b6bd6b2a..b981119e1e 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -706,7 +706,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do port_string = port == 80 ? '' : ":#{port}" protocol = options.delete(:protocol) || "http" - host = options.delete(:host) || "named.route.test" + host = options.delete(:host) || "test.host" anchor = "##{options.delete(:anchor)}" if options.key?(:anchor) path = routes.generate(options) @@ -715,27 +715,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def request - @request ||= MockRequest.new(:host => "named.route.test", :method => :get) - end - end - - class MockRequest - attr_accessor :path, :path_parameters, :host, :subdomains, :domain, :method - - def initialize(values={}) - values.each { |key, value| send("#{key}=", value) } - if values[:host] - subdomain, self.domain = values[:host].split(/\./, 2) - self.subdomains = [subdomain] - end - end - - def protocol - "http://" - end - - def host_with_port - (subdomains * '.') + '.' + domain + @request ||= ActionController::TestRequest.new end end @@ -900,7 +880,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_basic_named_route rs.add_named_route :home, '', :controller => 'content', :action => 'list' x = setup_for_named_route - assert_equal("http://named.route.test/", + assert_equal("http://test.host/", x.send(:home_url)) end @@ -908,7 +888,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do rs.add_named_route :home, '', :controller => 'content', :action => 'list' x = setup_for_named_route ActionController::Base.relative_url_root = "/foo" - assert_equal("http://named.route.test/foo/", + assert_equal("http://test.host/foo/", x.send(:home_url)) assert_equal "/foo/", x.send(:home_path) ActionController::Base.relative_url_root = nil @@ -917,14 +897,14 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_named_route_with_option rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page' x = setup_for_named_route - assert_equal("http://named.route.test/page/new%20stuff", + assert_equal("http://test.host/page/new%20stuff", x.send(:page_url, :title => 'new stuff')) end def test_named_route_with_default rs.add_named_route :page, 'page/:title', :controller => 'content', :action => 'show_page', :title => 'AboutPage' x = setup_for_named_route - assert_equal("http://named.route.test/page/AboutRails", + assert_equal("http://test.host/page/AboutRails", x.send(:page_url, :title => "AboutRails")) end @@ -932,21 +912,21 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_named_route_with_name_prefix rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :name_prefix => 'my_' x = setup_for_named_route - assert_equal("http://named.route.test/page", + assert_equal("http://test.host/page", x.send(:my_page_url)) end def test_named_route_with_path_prefix rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => 'my' x = setup_for_named_route - assert_equal("http://named.route.test/my/page", + assert_equal("http://test.host/my/page", x.send(:page_url)) end def test_named_route_with_nested_controller rs.add_named_route :users, 'admin/user', :controller => 'admin/user', :action => 'index' x = setup_for_named_route - assert_equal("http://named.route.test/admin/user", + assert_equal("http://test.host/admin/user", x.send(:users_url)) end @@ -985,7 +965,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do map.root :controller => "hello" end x = setup_for_named_route - assert_equal("http://named.route.test/", x.send(:root_url)) + assert_equal("http://test.host/", x.send(:root_url)) assert_equal("/", x.send(:root_path)) end @@ -1001,7 +981,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do # x.send(:article_url, :title => 'hi') # ) assert_equal( - "http://named.route.test/page/2005/6/10/hi", + "http://test.host/page/2005/6/10/hi", x.send(:article_url, :title => 'hi', :day => 10, :year => 2005, :month => 6) ) end @@ -1202,7 +1182,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do assert_equal '/test', rs.generate(:controller => 'post', :action => 'show', :year => nil) x = setup_for_named_route - assert_equal("http://named.route.test/test", + assert_equal("http://test.host/test", x.send(:blog_url)) end @@ -1249,7 +1229,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do assert_equal '/', rs.generate(:controller => 'content') x = setup_for_named_route - assert_equal("http://named.route.test/", + assert_equal("http://test.host/", x.send(:home_url)) end @@ -1591,7 +1571,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end def request - @request ||= MockRequest.new(:host => "named.routes.test", :method => :get) + @request ||= ActionController::TestRequest.new end def test_generate_extras @@ -1692,13 +1672,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_named_route_url_method controller = setup_named_route_test - assert_equal "http://named.route.test/people/5", controller.send(:show_url, :id => 5) + assert_equal "http://test.host/people/5", controller.send(:show_url, :id => 5) assert_equal "/people/5", controller.send(:show_path, :id => 5) - assert_equal "http://named.route.test/people", controller.send(:index_url) + assert_equal "http://test.host/people", controller.send(:index_url) assert_equal "/people", controller.send(:index_path) - assert_equal "http://named.route.test/admin/users", controller.send(:users_url) + assert_equal "http://test.host/admin/users", controller.send(:users_url) assert_equal '/admin/users', controller.send(:users_path) assert_equal '/admin/users', set.generate(controller.send(:hash_for_users_url), {:controller => 'users', :action => 'index'}) end @@ -1706,28 +1686,28 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_named_route_url_method_with_anchor controller = setup_named_route_test - assert_equal "http://named.route.test/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location') + assert_equal "http://test.host/people/5#location", controller.send(:show_url, :id => 5, :anchor => 'location') assert_equal "/people/5#location", controller.send(:show_path, :id => 5, :anchor => 'location') - assert_equal "http://named.route.test/people#location", controller.send(:index_url, :anchor => 'location') + assert_equal "http://test.host/people#location", controller.send(:index_url, :anchor => 'location') assert_equal "/people#location", controller.send(:index_path, :anchor => 'location') - assert_equal "http://named.route.test/admin/users#location", controller.send(:users_url, :anchor => 'location') + assert_equal "http://test.host/admin/users#location", controller.send(:users_url, :anchor => 'location') assert_equal '/admin/users#location', controller.send(:users_path, :anchor => 'location') - assert_equal "http://named.route.test/people/go/7/hello/joe/5#location", + assert_equal "http://test.host/people/go/7/hello/joe/5#location", controller.send(:multi_url, 7, "hello", 5, :anchor => 'location') - assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar#location", + assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar#location", controller.send(:multi_url, 7, "hello", 5, :baz => "bar", :anchor => 'location') - assert_equal "http://named.route.test/people?baz=bar#location", + assert_equal "http://test.host/people?baz=bar#location", controller.send(:index_url, :baz => "bar", :anchor => 'location') end def test_named_route_url_method_with_port controller = setup_named_route_test - assert_equal "http://named.route.test:8080/people/5", controller.send(:show_url, 5, :port=>8080) + assert_equal "http://test.host:8080/people/5", controller.send(:show_url, 5, :port=>8080) end def test_named_route_url_method_with_host @@ -1737,30 +1717,30 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do def test_named_route_url_method_with_protocol controller = setup_named_route_test - assert_equal "https://named.route.test/people/5", controller.send(:show_url, 5, :protocol => "https") + assert_equal "https://test.host/people/5", controller.send(:show_url, 5, :protocol => "https") end def test_named_route_url_method_with_ordered_parameters controller = setup_named_route_test - assert_equal "http://named.route.test/people/go/7/hello/joe/5", + assert_equal "http://test.host/people/go/7/hello/joe/5", controller.send(:multi_url, 7, "hello", 5) end def test_named_route_url_method_with_ordered_parameters_and_hash controller = setup_named_route_test - assert_equal "http://named.route.test/people/go/7/hello/joe/5?baz=bar", + assert_equal "http://test.host/people/go/7/hello/joe/5?baz=bar", controller.send(:multi_url, 7, "hello", 5, :baz => "bar") end def test_named_route_url_method_with_ordered_parameters_and_empty_hash controller = setup_named_route_test - assert_equal "http://named.route.test/people/go/7/hello/joe/5", + assert_equal "http://test.host/people/go/7/hello/joe/5", controller.send(:multi_url, 7, "hello", 5, {}) end def test_named_route_url_method_with_no_positional_arguments controller = setup_named_route_test - assert_equal "http://named.route.test/people?baz=bar", + assert_equal "http://test.host/people?baz=bar", controller.send(:index_url, :baz => "bar") end @@ -1896,49 +1876,54 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/people" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("index", request.path_parameters[:action]) + request.recycle! - request.method = :post + request.env["REQUEST_METHOD"] = "POST" assert_nothing_raised { set.recognize(request) } assert_equal("create", request.path_parameters[:action]) + request.recycle! - request.method = :put + request.env["REQUEST_METHOD"] = "PUT" assert_nothing_raised { set.recognize(request) } assert_equal("update", request.path_parameters[:action]) + request.recycle! - begin - request.method = :bacon + assert_raises(ActionController::UnknownHttpMethod) { + request.env["REQUEST_METHOD"] = "BACON" set.recognize(request) - flunk 'Should have raised NotImplemented' - rescue ActionController::NotImplemented => e - assert_equal [:get, :post, :put, :delete], e.allowed_methods - end + } + request.recycle! request.path = "/people/5" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("show", request.path_parameters[:action]) assert_equal("5", request.path_parameters[:id]) + request.recycle! - request.method = :put + request.env["REQUEST_METHOD"] = "PUT" assert_nothing_raised { set.recognize(request) } assert_equal("update", request.path_parameters[:action]) assert_equal("5", request.path_parameters[:id]) + request.recycle! - request.method = :delete + request.env["REQUEST_METHOD"] = "DELETE" assert_nothing_raised { set.recognize(request) } assert_equal("destroy", request.path_parameters[:action]) assert_equal("5", request.path_parameters[:id]) + request.recycle! begin - request.method = :post + request.env["REQUEST_METHOD"] = "POST" set.recognize(request) flunk 'Should have raised MethodNotAllowed' rescue ActionController::MethodNotAllowed => e assert_equal [:get, :put, :delete], e.allowed_methods end + request.recycle! ensure Object.send(:remove_const, :PeopleController) @@ -1954,13 +1939,13 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/people" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("people", request.path_parameters[:controller]) assert_equal("index", request.path_parameters[:action]) request.path = "/" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("people", request.path_parameters[:controller]) assert_equal("index", request.path_parameters[:action]) @@ -1978,7 +1963,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/articles/2005/11/05/a-very-interesting-article" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("permalink", request.path_parameters[:action]) assert_equal("2005", request.path_parameters[:year]) @@ -2015,17 +2000,19 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/people/5" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("show", request.path_parameters[:action]) assert_equal("5", request.path_parameters[:id]) + request.recycle! - request.method = :put + request.env["REQUEST_METHOD"] = "PUT" assert_nothing_raised { set.recognize(request) } assert_equal("update", request.path_parameters[:action]) + request.recycle! request.path = "/people/5.png" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("show", request.path_parameters[:action]) assert_equal("5", request.path_parameters[:id]) @@ -2050,7 +2037,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do set.draw { |map| map.root :controller => "people" } request.path = "" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("people", request.path_parameters[:controller]) assert_equal("index", request.path_parameters[:action]) @@ -2070,7 +2057,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/api/inventory" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("api/products", request.path_parameters[:controller]) assert_equal("inventory", request.path_parameters[:action]) @@ -2090,7 +2077,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/api" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("api/products", request.path_parameters[:controller]) assert_equal("index", request.path_parameters[:action]) @@ -2110,7 +2097,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/prefix/inventory" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("api/products", request.path_parameters[:controller]) assert_equal("inventory", request.path_parameters[:action]) @@ -2246,7 +2233,7 @@ uses_mocha 'LegacyRouteSet, Route, RouteSet and RouteLoading' do end request.path = "/projects/1/milestones" - request.method = :get + request.env["REQUEST_METHOD"] = "GET" assert_nothing_raised { set.recognize(request) } assert_equal("milestones", request.path_parameters[:controller]) assert_equal("index", request.path_parameters[:action]) diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index c4349bfc7f..1b7486ad34 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -119,6 +119,31 @@ class SendFileTest < Test::Unit::TestCase assert_equal 'private', h['Cache-Control'] end + def test_send_file_headers_with_mime_lookup_with_symbol + options = { + :length => 1, + :type => :png + } + + @controller.headers = {} + @controller.send(:send_file_headers!, options) + + headers = @controller.headers + + assert_equal 'image/png', headers['Content-Type'] + end + + + def test_send_file_headers_with_bad_symbol + options = { + :length => 1, + :type => :this_type_is_not_registered + } + + @controller.headers = {} + assert_raises(ArgumentError){ @controller.send(:send_file_headers!, options) } + end + %w(file data).each do |method| define_method "test_send_#{method}_status" do @controller.options = { :stream => false, :status => 500 } diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 69aec59dc0..d349c18d1d 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -25,7 +25,7 @@ class CookieStoreTest < ActionController::IntegrationTest def set_session_value session[:foo] = "bar" - render :text => Marshal.dump(session.to_hash) + render :text => Verifier.generate(session.to_hash) end def get_session_value @@ -94,8 +94,7 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do get '/set_session_value' assert_response :success - session_payload = Verifier.generate(Marshal.load(response.body)) - assert_equal ["_myapp_session=#{session_payload}; path=/"], + assert_equal ["_myapp_session=#{response.body}; path=/"], headers['Set-Cookie'] end end @@ -148,8 +147,8 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do get '/set_session_value' assert_response :success - session_payload = Verifier.generate(Marshal.load(response.body)) - assert_equal ["_myapp_session=#{session_payload}; path=/"], + session_payload = response.body + assert_equal ["_myapp_session=#{response.body}; path=/"], headers['Set-Cookie'] get '/call_reset_session' diff --git a/actionpack/test/template/date_helper_i18n_test.rb b/actionpack/test/template/date_helper_i18n_test.rb index dc9616db3b..fac30da128 100644 --- a/actionpack/test/template/date_helper_i18n_test.rb +++ b/actionpack/test/template/date_helper_i18n_test.rb @@ -78,6 +78,8 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase uses_mocha 'date_helper_select_tags_i18n_tests' do def setup + @prompt_defaults = {:year => 'Year', :month => 'Month', :day => 'Day', :hour => 'Hour', :minute => 'Minute', :second => 'Seconds'} + I18n.stubs(:translate).with(:'date.month_names', :locale => 'en').returns Date::MONTHNAMES end @@ -98,6 +100,15 @@ class DateHelperSelectTagsI18nTests < Test::Unit::TestCase select_month(8, :locale => 'en', :use_short_month => true) end + def test_date_or_time_select_translates_prompts + @prompt_defaults.each do |key, prompt| + I18n.expects(:translate).with(('datetime.prompts.' + key.to_s).to_sym, :locale => 'en').returns prompt + end + + I18n.expects(:translate).with(:'date.order', :locale => 'en').returns [:year, :month, :day] + datetime_select('post', 'updated_at', :locale => 'en', :include_seconds => true, :prompt => true) + end + # date_or_time_select def test_date_or_time_select_given_an_order_options_does_not_translate_order diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 49ba140c23..6ec01b7a8f 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -153,6 +153,22 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_day(16, {}, :class => 'selector') end + def test_select_day_with_default_prompt + expected = %(<select id="date_day" name="date[day]">\n) + expected << %(<option value="">Day</option>\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 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<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, select_day(16, :prompt => true) + end + + def test_select_day_with_custom_prompt + expected = %(<select id="date_day" name="date[day]">\n) + expected << %(<option value="">Choose day</option>\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 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<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, select_day(16, :prompt => 'Choose day') + end + def test_select_month expected = %(<select id="date_month" name="date[month]">\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">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">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) @@ -276,6 +292,22 @@ class DateHelperTest < ActionView::TestCase #assert result.include?('<option value="1">January') end + def test_select_month_with_default_prompt + expected = %(<select id="date_month" name="date[month]">\n) + expected << %(<option value="">Month</option>\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 value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">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" + + assert_dom_equal expected, select_month(8, :prompt => true) + end + + def test_select_month_with_custom_prompt + expected = %(<select id="date_month" name="date[month]">\n) + expected << %(<option value="">Choose month</option>\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 value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">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" + + assert_dom_equal expected, select_month(8, :prompt => 'Choose month') + end + def test_select_year expected = %(<select id="date_year" name="date[year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) @@ -344,6 +376,22 @@ class DateHelperTest < ActionView::TestCase #assert result.include?('<option value="2003"') end + def test_select_year_with_default_prompt + expected = %(<select id="date_year" name="date[year]">\n) + expected << %(<option value="">Year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => true) + end + + def test_select_year_with_custom_prompt + expected = %(<select id="date_year" name="date[year]">\n) + expected << %(<option value="">Choose year</option>\n<option value="2003">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + assert_dom_equal expected, select_year(nil, :start_year => 2003, :end_year => 2005, :prompt => 'Choose year') + end + def test_select_hour expected = %(<select id="date_hour" name="date[hour]">\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" selected="selected">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) @@ -392,6 +440,22 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), {}, :class => 'selector', :accesskey => 'M') end + def test_select_hour_with_default_prompt + expected = %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="">Hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + end + + def test_select_hour_with_custom_prompt + expected = %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="">Choose hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + assert_dom_equal expected, select_hour(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose hour') + end + def test_select_minute expected = %(<select id="date_minute" name="date[minute]">\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" selected="selected">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">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) @@ -470,6 +534,22 @@ class DateHelperTest < ActionView::TestCase #assert result.include?('<option value="00">00') end + def test_select_minute_with_default_prompt + expected = %(<select id="date_minute" name="date[minute]">\n) + expected << %(<option value="">Minute</option>\n<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" selected="selected">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">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, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + end + + def test_select_minute_with_custom_prompt + expected = %(<select id="date_minute" name="date[minute]">\n) + expected << %(<option value="">Choose minute</option>\n<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" selected="selected">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">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, select_minute(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose minute') + end + def test_select_second expected = %(<select id="date_second" name="date[second]">\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" selected="selected">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">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) @@ -524,6 +604,22 @@ class DateHelperTest < ActionView::TestCase #assert result.include?('<option value="00">00') end + def test_select_second_with_default_prompt + expected = %(<select id="date_second" name="date[second]">\n) + expected << %(<option value="">Seconds</option>\n<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" selected="selected">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">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, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true) + end + + def test_select_second_with_custom_prompt + expected = %(<select id="date_second" name="date[second]">\n) + expected << %(<option value="">Choose seconds</option>\n<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" selected="selected">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">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, select_second(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => 'Choose seconds') + end + def test_select_date expected = %(<select id="date_first_year" name="date[first][year]">\n) expected << %(<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) @@ -914,6 +1010,57 @@ class DateHelperTest < ActionView::TestCase assert_nothing_raised { select_datetime(Date.today) } end + def test_select_datetime_with_default_prompt + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="">Year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_month" name="date[first][month]">\n) + expected << %(<option value="">Month</option>\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 value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">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="date_first_day" name="date[first][day]">\n) + expected << %(<option value="">Day</option>\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 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<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 << %(<select id="date_first_hour" name="date[first][hour]">\n) + expected << %(<option value="">Hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + expected << %(<select id="date_first_minute" name="date[first][minute]">\n) + expected << %(<option value="">Minute</option>\n<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" selected="selected">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">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, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, + :prefix => "date[first]", :prompt => true) + end + + def test_select_datetime_with_custom_prompt + + expected = %(<select id="date_first_year" name="date[first][year]">\n) + expected << %(<option value="">Choose year</option>\n<option value="2003" selected="selected">2003</option>\n<option value="2004">2004</option>\n<option value="2005">2005</option>\n) + expected << "</select>\n" + + expected << %(<select id="date_first_month" name="date[first][month]">\n) + expected << %(<option value="">Choose month</option>\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 value="6">June</option>\n<option value="7">July</option>\n<option value="8" selected="selected">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="date_first_day" name="date[first][day]">\n) + expected << %(<option value="">Choose day</option>\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 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<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 << %(<select id="date_first_hour" name="date[first][hour]">\n) + expected << %(<option value="">Choose hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + expected << %(<select id="date_first_minute" name="date[first][minute]">\n) + expected << %(<option value="">Choose minute</option>\n<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" selected="selected">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">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, select_datetime(Time.mktime(2003, 8, 16, 8, 4, 18), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", + :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year', :hour => 'Choose hour', :minute => 'Choose minute'}) + end + def test_select_time expected = %(<select id="date_hour" name="date[hour]">\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" selected="selected">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) @@ -995,6 +1142,40 @@ class DateHelperTest < ActionView::TestCase assert_nothing_raised { select_time(Date.today) } end + def test_select_time_with_default_prompt + expected = %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="">Hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + expected << %(<select id="date_minute" name="date[minute]">\n) + expected << %(<option value="">Minute</option>\n<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" selected="selected">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">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" + + expected << %(<select id="date_second" name="date[second]">\n) + expected << %(<option value="">Seconds</option>\n<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" selected="selected">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">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, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :include_seconds => true, :prompt => true) + end + + def test_select_time_with_custom_prompt + + expected = %(<select id="date_hour" name="date[hour]">\n) + expected << %(<option value="">Choose hour</option>\n<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" selected="selected">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) + expected << "</select>\n" + + expected << %(<select id="date_minute" name="date[minute]">\n) + expected << %(<option value="">Choose minute</option>\n<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" selected="selected">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">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" + + expected << %(<select id="date_second" name="date[second]">\n) + expected << %(<option value="">Choose seconds</option>\n<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" selected="selected">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">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, select_time(Time.mktime(2003, 8, 16, 8, 4, 18), :prompt => true, :include_seconds => true, + :prompt => {:hour => 'Choose hour', :minute => 'Choose minute', :second => 'Choose seconds'}) + end + def test_date_select @post = Post.new @post.written_on = Date.new(2004, 6, 15) @@ -1277,6 +1458,46 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", { :date_separator => " / " }) end + def test_date_select_with_default_prompt + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="">Year</option>\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 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_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="">Month</option>\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 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_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="">Day</option>\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 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", :prompt => true) + end + + def test_date_select_with_custom_prompt + @post = Post.new + @post.written_on = Date.new(2004, 6, 15) + + expected = %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="">Choose year</option>\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 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_written_on_2i" name="post[written_on(2i)]">\n} + expected << %{<option value="">Choose month</option>\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 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_written_on_3i" name="post[written_on(3i)]">\n} + expected << %{<option value="">Choose day</option>\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 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", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day'}) + end + def test_time_select @post = Post.new @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) @@ -1403,6 +1624,48 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, time_select("post", "written_on", { :time_separator => " - ", :include_seconds => true }) end + def test_time_select_with_default_prompt + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + expected << %(<option value="">Hour</option>\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + expected << %(<option value="">Minute</option>\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", :prompt => true) + end + + def test_time_select_with_custom_prompt + @post = Post.new + @post.written_on = Time.local(2004, 6, 15, 15, 16, 35) + + expected = %{<input type="hidden" id="post_written_on_1i" name="post[written_on(1i)]" value="2004" />\n} + expected << %{<input type="hidden" id="post_written_on_2i" name="post[written_on(2i)]" value="6" />\n} + expected << %{<input type="hidden" id="post_written_on_3i" name="post[written_on(3i)]" value="15" />\n} + + expected << %(<select id="post_written_on_4i" name="post[written_on(4i)]">\n) + expected << %(<option value="">Choose hour</option>\n) + 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + expected << " : " + expected << %(<select id="post_written_on_5i" name="post[written_on(5i)]">\n) + expected << %(<option value="">Choose minute</option>\n) + 0.upto(59) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 16}>#{sprintf("%02d", i)}</option>\n) } + expected << "</select>\n" + + assert_dom_equal expected, time_select("post", "written_on", :prompt => {:hour => 'Choose hour', :minute => 'Choose minute'}) + end + def test_datetime_select @post = Post.new @post.updated_at = Time.local(2004, 6, 15, 16, 35) @@ -1526,6 +1789,64 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, datetime_select("post", "updated_at", { :date_separator => " / ", :datetime_separator => " , ", :time_separator => " - ", :include_seconds => true }) end + def test_datetime_select_with_default_prompt + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + expected << %{<option value="">Year</option>\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 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_updated_at_2i" name="post[updated_at(2i)]">\n} + expected << %{<option value="">Month</option>\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 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_updated_at_3i" name="post[updated_at(3i)]">\n} + expected << %{<option value="">Day</option>\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 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 << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + expected << %{<option value="">Hour</option>\n<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_updated_at_5i" name="post[updated_at(5i)]">\n} + expected << %{<option value="">Minute</option>\n<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, datetime_select("post", "updated_at", :prompt => true) + end + + def test_datetime_select_with_custom_prompt + @post = Post.new + @post.updated_at = Time.local(2004, 6, 15, 16, 35) + + expected = %{<select id="post_updated_at_1i" name="post[updated_at(1i)]">\n} + expected << %{<option value="">Choose year</option>\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 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_updated_at_2i" name="post[updated_at(2i)]">\n} + expected << %{<option value="">Choose month</option>\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 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_updated_at_3i" name="post[updated_at(3i)]">\n} + expected << %{<option value="">Choose day</option>\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 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 << " — " + + expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} + expected << %{<option value="">Choose hour</option>\n<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_updated_at_5i" name="post[updated_at(5i)]">\n} + expected << %{<option value="">Choose minute</option>\n<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, datetime_select("post", "updated_at", :prompt => {:year => 'Choose year', :month => 'Choose month', :day => 'Choose day', :hour => 'Choose hour', :minute => 'Choose minute'}) + end + def test_date_select_with_zero_value_and_no_start_year expected = %(<select id="date_first_year" name="date[first][year]">\n) (Date.today.year-5).upto(Date.today.year+1) { |y| expected << %(<option value="#{y}">#{y}</option>\n) } |