diff options
Diffstat (limited to 'actionpack')
95 files changed, 1331 insertions, 460 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 546adeb61d..8c9486cc63 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,4 +1,12 @@ -*Edge* +*2.3.2 [Final] (March 15, 2009)* + +* Fixed that redirection would just log the options, not the final url (which lead to "Redirected to #<Post:0x23150b8>") [DHH] + +* Added ability to pass in :public => true to fresh_when, stale?, and expires_in to make the request proxy cachable #2095 [Gregg Pollack] + +* Fixed that passing a custom form builder would be forwarded to nested fields_for calls #2023 [Eloy Duran/Nate Wiger] + +* Form option helpers now support disabled option tags and the use of lambdas for selecting/disabling option tags from collections #837 [Tekin] * Added partial scoping to TranslationHelper#translate, so if you call translate(".foo") from the people/index.html.erb template, you'll actually be calling I18n.translate("people.index.foo") [DHH] @@ -6,9 +14,6 @@ * Added localized rescue template when I18n.locale is set (ex: public/404.da.html) #1835 [José Valim] - -*2.3.0 [RC1] (February 1st, 2009)* - * Make the form_for and fields_for helpers support the new Active Record nested update options. #1202 [Eloy Duran] <% form_for @person do |person_form| %> diff --git a/actionpack/Rakefile b/actionpack/Rakefile index c389e5a8d6..6cacdf3c6e 100644 --- a/actionpack/Rakefile +++ b/actionpack/Rakefile @@ -80,8 +80,7 @@ spec = Gem::Specification.new do |s| s.has_rdoc = true s.requirements << 'none' - s.add_dependency('activesupport', '= 2.3.0' + PKG_BUILD) - s.add_dependency('rack', '>= 0.9.0') + s.add_dependency('activesupport', '= 2.3.2' + PKG_BUILD) s.require_path = 'lib' s.autorequire = 'action_controller' diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index ca826e7bfc..d03f4cb231 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -31,7 +31,12 @@ rescue LoadError end end -require 'action_controller/vendor/rack-1.0/rack' +begin + gem 'rack', '~> 1.0.0' + require 'rack' +rescue Gem::LoadError + require 'action_controller/vendor/rack-1.0/rack' +end module ActionController # TODO: Review explicit to see if they will automatically be handled by diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 4fc52c1470..6348961ff4 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -22,7 +22,7 @@ module ActionController #:nodoc: attr_reader :allowed_methods def initialize(*allowed_methods) - super("Only #{allowed_methods.to_sentence} requests are allowed.") + super("Only #{allowed_methods.to_sentence(:locale => :en)} requests are allowed.") @allowed_methods = allowed_methods end @@ -408,7 +408,7 @@ module ActionController #:nodoc: # Return an array containing the names of public methods that have been marked hidden from the action processor. # By default, all methods defined in ActionController::Base and included modules are hidden. - # More methods can be hidden using <tt>hide_actions</tt>. + # More methods can be hidden using <tt>hide_action</tt>. def hidden_actions read_inheritable_attribute(:hidden_actions) || write_inheritable_attribute(:hidden_actions, []) end @@ -908,12 +908,14 @@ module ActionController #:nodoc: end options = extra_options + elsif !options.is_a?(Hash) + extra_options[:partial] = options + options = extra_options end layout = pick_layout(options) response.layout = layout.path_without_format_and_extension if layout logger.info("Rendering template within #{layout.path_without_format_and_extension}") if logger && layout - layout = layout.path_without_format_and_extension if layout if content_type = options[:content_type] response.content_type = content_type.to_s @@ -982,6 +984,7 @@ module ActionController #:nodoc: # of sending it as the response body to the browser. def render_to_string(options = nil, &block) #:doc: render(options, &block) + response.body ensure response.content_type = nil erase_render_results @@ -1018,7 +1021,7 @@ module ActionController #:nodoc: # Clears the rendered results, allowing for another render to be performed. def erase_render_results #:nodoc: - response.body = nil + response.body = [] @performed_render = false end @@ -1101,7 +1104,6 @@ module ActionController #:nodoc: end response.redirected_to = options - logger.info("Redirected to #{options}") if logger && logger.info? case options # The scheme name consist of a letter followed by any combination of @@ -1124,6 +1126,7 @@ module ActionController #:nodoc: def redirect_to_full_url(url, status) raise DoubleRenderError if performed? + logger.info("Redirected to #{url}") if logger && logger.info? response.redirect(url, interpret_status(status)) @performed_redirect = true end @@ -1133,6 +1136,11 @@ module ActionController #:nodoc: # request is considered stale and should be generated from scratch. Otherwise, # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent. # + # Parameters: + # * <tt>:etag</tt> + # * <tt>:last_modified</tt> + # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # # Example: # # def show @@ -1153,20 +1161,34 @@ module ActionController #:nodoc: # Sets the etag, last_modified, or both on the response and renders a # "304 Not Modified" response if the request is already fresh. # + # Parameters: + # * <tt>:etag</tt> + # * <tt>:last_modified</tt> + # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # # Example: # # def show # @article = Article.find(params[:id]) - # fresh_when(:etag => @article, :last_modified => @article.created_at.utc) + # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true) # end # # This will render the show template if the request isn't sending a matching etag or # If-Modified-Since header and just a "304 Not Modified" response if there's a match. + # def fresh_when(options) - options.assert_valid_keys(:etag, :last_modified) + options.assert_valid_keys(:etag, :last_modified, :public) response.etag = options[:etag] if options[:etag] response.last_modified = options[:last_modified] if options[:last_modified] + + if options[:public] + cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + cache_control.delete("private") + cache_control.delete("no-cache") + cache_control << "public" + response.headers["Cache-Control"] = cache_control.join(', ') + end if request.fresh?(response) head :not_modified @@ -1178,15 +1200,26 @@ module ActionController #:nodoc: # # Examples: # expires_in 20.minutes - # expires_in 3.hours, :private => false - # expires in 3.hours, 'max-stale' => 5.hours, :private => nil, :public => true + # expires_in 3.hours, :public => true + # expires in 3.hours, 'max-stale' => 5.hours, :public => true # # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. def expires_in(seconds, options = {}) #:doc: - cache_options = { 'max-age' => seconds, 'private' => true }.symbolize_keys.merge!(options.symbolize_keys) - cache_options.delete_if { |k,v| v.nil? or v == false } - cache_control = cache_options.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} + cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + + cache_control << "max-age=#{seconds}" + cache_control.delete("no-cache") + if options[:public] + cache_control.delete("private") + cache_control << "public" + else + cache_control << "private" + end + + # This allows for additional headers to be passed through like 'max-stale' => 5.hours + cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} + response.headers["Cache-Control"] = cache_control.join(', ') end @@ -1215,13 +1248,12 @@ module ActionController #:nodoc: response.status = interpret_status(status || DEFAULT_RENDER_STATUS_CODE) if append_response - response.body ||= '' - response.body << text.to_s + response.body_parts << text.to_s else response.body = case text - when Proc then text - when nil then " " # Safari doesn't pass the headers of the return if the response is zero length - else text.to_s + when Proc then text + when nil then [" "] # Safari doesn't pass the headers of the return if the response is zero length + else [text.to_s] end end end @@ -1298,7 +1330,7 @@ module ActionController #:nodoc: 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 + raise UnknownAction, "No action responded to #{action_name}. Actions: #{action_methods.sort.to_sentence(:locale => :en)}", caller else raise e end diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index 34e1c3527f..87b5029e57 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -129,24 +129,23 @@ module ActionController #:nodoc: attr_reader :path, :extension class << self - def path_for(controller, options, infer_extension=true) + def path_for(controller, options, infer_extension = true) new(controller, options, infer_extension).path end end # When true, infer_extension will look up the cache path extension from the request's path & format. - # This is desirable when reading and writing the cache, but not when expiring the cache - expire_action should expire the same files regardless of the request format. - def initialize(controller, options = {}, infer_extension=true) - if infer_extension and options.is_a? Hash - request_extension = extract_extension(controller.request) - options = options.reverse_merge(:format => request_extension) + # This is desirable when reading and writing the cache, but not when expiring the cache - + # expire_action should expire the same files regardless of the request format. + def initialize(controller, options = {}, infer_extension = true) + if infer_extension + extract_extension(controller.request) + options = options.reverse_merge(:format => @extension) if options.is_a?(Hash) end + path = controller.url_for(options).split('://').last normalize!(path) - if infer_extension - @extension = request_extension - add_extension!(path, @extension) - end + add_extension!(path, @extension) @path = URI.unescape(path) end @@ -162,13 +161,7 @@ module ActionController #:nodoc: def extract_extension(request) # Don't want just what comes after the last '.' to accommodate multi part extensions # such as tar.gz. - extension = request.path[/^[^.]+\.(.+)$/, 1] - - # If there's no extension in the path, check request.format - if extension.nil? - extension = request.cache_format - end - extension + @extension = request.path[/^[^.]+\.(.+)$/, 1] || request.cache_format end end end diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb index ec40b5c4e6..07931e4a4a 100644 --- a/actionpack/lib/action_controller/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatcher.rb @@ -13,7 +13,6 @@ module ActionController end if defined?(ActiveRecord) - after_dispatch :checkin_connections to_prepare(:activerecord_instantiate_observers) { ActiveRecord::Base.instantiate_observers } end @@ -115,11 +114,5 @@ module ActionController def flush_logger Base.logger.flush end - - def checkin_connections - # Don't return connection (and peform implicit rollback) if this request is a part of integration test - return if @env.key?("rack.test") - ActiveRecord::Base.clear_active_connections! - end end end diff --git a/actionpack/lib/action_controller/http_authentication.rb b/actionpack/lib/action_controller/http_authentication.rb index 2ccbc22420..b6b5267c66 100644 --- a/actionpack/lib/action_controller/http_authentication.rb +++ b/actionpack/lib/action_controller/http_authentication.rb @@ -68,8 +68,11 @@ module ActionController # # Simple Digest example: # + # require 'digest/md5' # class PostsController < ApplicationController - # USERS = {"dhh" => "secret"} + # REALM = "SuperSecret" + # USERS = {"dhh" => "secret", #plain text password + # "dap" => Digest:MD5::hexdigest(["dap",REALM,"secret"].join(":")) #ha1 digest password # # before_filter :authenticate, :except => [:index] # @@ -83,14 +86,18 @@ module ActionController # # private # def authenticate - # authenticate_or_request_with_http_digest(realm) do |username| + # authenticate_or_request_with_http_digest(REALM) do |username| # USERS[username] # end # end # end # - # NOTE: The +authenticate_or_request_with_http_digest+ 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. + # NOTE: The +authenticate_or_request_with_http_digest+ block must return the user's password or the ha1 digest hash so the framework can appropriately + # hash to check the user's credentials. Returning +nil+ will cause authentication to fail. + # Storing the ha1 hash: MD5(username:realm:password), is better than storing a plain password. If + # the password file or database is compromised, the attacker would be able to use the ha1 hash to + # authenticate as the user at this +realm+, but would not have the user's password to try using at + # other sites. # # On shared hosts, Apache sometimes doesn't pass authentication headers to # FCGI instances. If your environment matches this description and you cannot @@ -177,26 +184,37 @@ module ActionController end # Raises error unless the request credentials response value matches the expected value. + # First try the password as a ha1 digest password. If this fails, then try it as a plain + # text password. def validate_digest_response(request, realm, &password_procedure) credentials = decode_credentials_header(request) valid_nonce = validate_nonce(request, credentials[:nonce]) - if valid_nonce && realm == credentials[:realm] && opaque(request.session.session_id) == credentials[:opaque] + if valid_nonce && realm == credentials[:realm] && opaque == credentials[:opaque] password = password_procedure.call(credentials[:username]) - expected = expected_response(request.env['REQUEST_METHOD'], credentials[:uri], credentials, password) - expected == credentials[:response] + + [true, false].any? do |password_is_ha1| + expected = expected_response(request.env['REQUEST_METHOD'], request.env['REQUEST_URI'], credentials, password, password_is_ha1) + expected == credentials[:response] + end end 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(':')) + # Optional parameter +password_is_ha1+ is set to +true+ by default, since best practice is to store ha1 digest instead + # of a plain-text password. + def expected_response(http_method, uri, credentials, password, password_is_ha1=true) + ha1 = password_is_ha1 ? password : ha1(credentials, password) 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) + def ha1(credentials, password) + ::Digest::MD5.hexdigest([credentials[:username], credentials[:realm], password].join(':')) + end + + def encode_credentials(http_method, credentials, password, password_is_ha1) + credentials[:response] = expected_response(http_method, credentials[:uri], credentials, password, password_is_ha1) "Digest " + credentials.sort_by {|x| x[0].to_s }.inject([]) {|a, v| a << "#{v[0]}='#{v[1]}'" }.join(', ') end @@ -213,8 +231,7 @@ module ActionController end def authentication_header(controller, realm) - session_id = controller.request.session.session_id - controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce(session_id)}", opaque="#{opaque(session_id)}") + controller.headers["WWW-Authenticate"] = %(Digest realm="#{realm}", qop="auth", algorithm=MD5, nonce="#{nonce}", opaque="#{opaque}") end def authentication_request(controller, realm, message = nil) @@ -252,23 +269,36 @@ module ActionController # 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(session_id, time = Time.now) + # The nonce is opaque to the client. Composed of Time, and hash of Time with secret + # key from the Rails session secret generated upon creation of project. Ensures + # the time cannot be modifed by client. + def nonce(time = Time.now) t = time.to_i - hashed = [t, session_id] + hashed = [t, secret_key] digest = ::Digest::MD5.hexdigest(hashed.join(":")) Base64.encode64("#{t}:#{digest}").gsub("\n", '') end - def validate_nonce(request, value) + # Might want a shorter timeout depending on whether the request + # is a PUT or POST, and if client is browser or web service. + # Can be much shorter if the Stale directive is implemented. This would + # allow a user to use new nonce without prompting user again for their + # username and password. + def validate_nonce(request, value, seconds_to_timeout=5*60) t = Base64.decode64(value).split(":").first.to_i - nonce(request.session.session_id, t) == value && (t - Time.now.to_i).abs <= 10 * 60 + nonce(t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout end - # Opaque based on digest of session_id - def opaque(session_id) - Base64.encode64(::Digest::MD5::hexdigest(session_id)).gsub("\n", '') + # Opaque based on random generation - but changing each request? + def opaque() + ::Digest::MD5.hexdigest(secret_key) end + + # Set in /initializers/session_store.rb, and loaded even if sessions are not in use. + def secret_key + ActionController::Base.session_options[:secret] + end + end end end diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb index 1c05ab0bf6..fda6b639d1 100644 --- a/actionpack/lib/action_controller/integration.rb +++ b/actionpack/lib/action_controller/integration.rb @@ -5,7 +5,7 @@ require 'active_support/test_case' module ActionController module Integration #:nodoc: # An integration Session instance represents a set of requests and responses - # performed sequentially by some virtual user. Becase you can instantiate + # performed sequentially by some virtual user. Because you can instantiate # multiple sessions and run them side-by-side, you can also mimic (to some # limited extent) multiple simultaneous users interacting with your system. # @@ -332,11 +332,13 @@ module ActionController @cookies[name] = value end - @body = "" if body.is_a?(String) - @body << body + @body_parts = [body] + @body = body else - body.each { |part| @body << part } + @body_parts = [] + body.each { |part| @body_parts << part.to_s } + @body = @body_parts.join end if @controller = ActionController::Base.last_instantiation @@ -349,7 +351,7 @@ module ActionController @response = Response.new @response.status = status.to_s @response.headers.replace(@headers) - @response.body = @body + @response.body = @body_parts end # Decorate the response with the standard behavior of the diff --git a/actionpack/lib/action_controller/layout.rb b/actionpack/lib/action_controller/layout.rb index a0db7acf72..6ec0c1b304 100644 --- a/actionpack/lib/action_controller/layout.rb +++ b/actionpack/lib/action_controller/layout.rb @@ -198,7 +198,7 @@ module ActionController #:nodoc: # is called and the return value is used. Likewise if the layout was specified as an inline method (through a proc or method # object). If the layout was defined without a directory, layouts is assumed. So <tt>layout "weblog/standard"</tt> will return # weblog/standard, but <tt>layout "standard"</tt> will return layouts/standard. - def active_layout(passed_layout = nil) + def active_layout(passed_layout = nil, options = {}) layout = passed_layout || default_layout return layout if layout.respond_to?(:render) @@ -207,21 +207,23 @@ module ActionController #:nodoc: when Proc then layout.call(self) else layout end - - find_layout(active_layout, @template.template_format) if active_layout + + find_layout(active_layout, default_template_format, options[:html_fallback]) if active_layout end private def default_layout #:nodoc: - layout = self.class.read_inheritable_attribute(:layout) unless default_template_format == :js + layout = self.class.read_inheritable_attribute(:layout) return layout unless self.class.read_inheritable_attribute(:auto_layout) find_layout(layout, default_template_format) rescue ActionView::MissingTemplate nil end - def find_layout(layout, *formats) #:nodoc: - view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", *formats) + def find_layout(layout, format, html_fallback=false) #:nodoc: + view_paths.find_template(layout.to_s =~ /layouts\// ? layout : "layouts/#{layout}", format, html_fallback) + rescue ActionView::MissingTemplate + raise if Mime::Type.lookup_by_extension(format.to_s).html? end def pick_layout(options) @@ -232,7 +234,7 @@ module ActionController #:nodoc: when NilClass, TrueClass active_layout if action_has_layout? && candidate_for_layout?(:template => default_template_name) else - active_layout(layout) + active_layout(layout, :html_fallback => true) end else active_layout if action_has_layout? && candidate_for_layout?(options) @@ -258,7 +260,12 @@ module ActionController #:nodoc: 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? + template_object = self.view_paths.find_template(template, default_template_format) + # this restores the behavior from 2.2.2, where response.template.template_format was reset + # to :html for :js requests with a matching html template. + # see v2.2.2, ActionView::Base, lines 328-330 + @real_format = :html if response.template.template_format == :js && template_object.format == "html" + !template_object.exempt_from_layout? rescue ActionView::MissingTemplate true end @@ -268,7 +275,7 @@ module ActionController #:nodoc: end def default_template_format - response.template.template_format + @real_format || response.template.template_format end end end diff --git a/actionpack/lib/action_controller/polymorphic_routes.rb b/actionpack/lib/action_controller/polymorphic_routes.rb index 924d1aa6bd..d9b614c237 100644 --- a/actionpack/lib/action_controller/polymorphic_routes.rb +++ b/actionpack/lib/action_controller/polymorphic_routes.rb @@ -163,7 +163,8 @@ module ActionController if parent.is_a?(Symbol) || parent.is_a?(String) string << "#{parent}_" else - string << "#{RecordIdentifier.__send__("singular_class_name", parent)}_" + string << "#{RecordIdentifier.__send__("plural_class_name", parent)}".singularize + string << "_" end end end @@ -171,7 +172,9 @@ module ActionController if record.is_a?(Symbol) || record.is_a?(String) route << "#{record}_" else - route << "#{RecordIdentifier.__send__("#{inflection}_class_name", record)}_" + route << "#{RecordIdentifier.__send__("plural_class_name", record)}" + route = route.singularize if inflection == :singular + route << "_" end action_prefix(options) + namespace + route + routing_type(options).to_s diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index 0e95cfc147..ef223f157c 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -32,7 +32,7 @@ module ActionController # <tt>:get</tt>. If the request \method is not listed in the HTTP_METHODS # constant above, an UnknownHttpMethod exception is raised. def request_method - @request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence}") + @request_method ||= HTTP_METHOD_LOOKUP[super] || raise(UnknownHttpMethod, "#{super}, accepted HTTP methods are #{HTTP_METHODS.to_sentence(:locale => :en)}") end # Returns the HTTP request \method used for action processing as a @@ -442,6 +442,7 @@ EOM end def reset_session + @env['rack.session.options'].delete(:id) @env['rack.session'] = {} end diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index 3af21967df..86abb7b2f4 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -91,7 +91,7 @@ module ActionController end def shallow_path_prefix - @shallow_path_prefix ||= "#{path_prefix unless @options[:shallow]}" + @shallow_path_prefix ||= @options[:shallow] ? @options[:namespace].try(:sub, /\/$/, '') : path_prefix end def member_path @@ -103,7 +103,7 @@ module ActionController end def shallow_name_prefix - @shallow_name_prefix ||= "#{name_prefix unless @options[:shallow]}" + @shallow_name_prefix ||= @options[:shallow] ? @options[:namespace].try(:gsub, /\//, '_') : name_prefix end def nesting_name_prefix @@ -630,7 +630,7 @@ module ActionController action_path = resource.options[:path_names][action] if resource.options[:path_names].is_a?(Hash) action_path ||= Base.resources_path_names[action] || action - map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m) + map_resource_routes(map, resource, action, "#{resource.member_path}#{resource.action_separator}#{action_path}", "#{action}_#{resource.shallow_name_prefix}#{resource.singular}", m, { :force_id => true }) end end end @@ -641,9 +641,9 @@ module ActionController map_resource_routes(map, resource, :destroy, resource.member_path, route_path) end - def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil) + def map_resource_routes(map, resource, action, route_path, route_name = nil, method = nil, resource_options = {} ) if resource.has_action?(action) - action_options = action_options_for(action, resource, method) + action_options = action_options_for(action, resource, method, resource_options) formatted_route_path = "#{route_path}.:format" if route_name && @set.named_routes[route_name.to_sym].nil? @@ -660,9 +660,10 @@ module ActionController end end - def action_options_for(action, resource, method = nil) + def action_options_for(action, resource, method = nil, resource_options = {}) default_options = { :action => action.to_s } require_id = !resource.kind_of?(SingletonResource) + force_id = resource_options[:force_id] && !resource.kind_of?(SingletonResource) case default_options[:action] when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) @@ -670,7 +671,7 @@ module ActionController when "show", "edit"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements(require_id)) when "update"; default_options.merge(add_conditions_for(resource.conditions, method || :put)).merge(resource.requirements(require_id)) when "destroy"; default_options.merge(add_conditions_for(resource.conditions, method || :delete)).merge(resource.requirements(require_id)) - else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements) + else default_options.merge(add_conditions_for(resource.conditions, method)).merge(resource.requirements(force_id)) end end end diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index ccff473df0..febe4ccf29 100644 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -40,14 +40,28 @@ module ActionController # :nodoc: delegate :default_charset, :to => 'ActionController::Base' def initialize - @status = 200 + super @header = Rack::Utils::HeaderHash.new(DEFAULT_HEADERS) + @session, @assigns = [], [] + end - @writer = lambda { |x| @body << x } - @block = nil + def body + str = '' + each { |part| str << part.to_s } + str + end - @body = "", - @session, @assigns = [], [] + def body=(body) + @body = + if body.is_a?(String) + [body] + else + body + end + end + + def body_parts + @body end def location; headers['Location'] end @@ -152,7 +166,7 @@ module ActionController # :nodoc: @writer = lambda { |x| callback.call(x) } @body.call(self, self) elsif @body.is_a?(String) - @body.each_line(&callback) + callback.call(@body) else @body.each(&callback) end @@ -162,7 +176,8 @@ module ActionController # :nodoc: end def write(str) - @writer.call str.to_s + str = str.to_s + @writer.call str str end @@ -186,7 +201,7 @@ module ActionController # :nodoc: if request && request.etag_matches?(etag) self.status = '304 Not Modified' - self.body = '' + self.body = [] end set_conditional_cache_control! @@ -195,7 +210,11 @@ module ActionController # :nodoc: def nonempty_ok_response? ok = !status || status.to_s[0..2] == '200' - ok && body.is_a?(String) && !body.empty? + ok && string_body? + end + + def string_body? + !body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) } end def set_conditional_cache_control! @@ -216,8 +235,8 @@ module ActionController # :nodoc: headers.delete('Content-Length') elsif length = headers['Content-Length'] headers['Content-Length'] = length.to_s - elsif !body.respond_to?(:call) && (!status || status.to_s[0..2] != '304') - headers["Content-Length"] = (body.respond_to?(:bytesize) ? body.bytesize : body.size).to_s + elsif string_body? && (!status || status.to_s[0..2] != '304') + headers["Content-Length"] = Rack::Utils.bytesize(body).to_s end end diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb index a2141a77dc..c0eb61340b 100644 --- a/actionpack/lib/action_controller/routing.rb +++ b/actionpack/lib/action_controller/routing.rb @@ -267,7 +267,7 @@ module ActionController module Routing SEPARATORS = %w( / . ? ) - HTTP_METHODS = [:get, :head, :post, :put, :delete] + HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] ALLOWED_REQUIREMENTS_FOR_OPTIMISATION = [:controller, :action].to_set diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index 44d759444a..d9590c88b8 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -159,7 +159,8 @@ module ActionController path = "/#{path}" unless path[0] == ?/ path = "#{path}/" unless path[-1] == ?/ - path = "/#{options[:path_prefix].to_s.gsub(/^\//,'')}#{path}" if options[:path_prefix] + prefix = options[:path_prefix].to_s.gsub(/^\//,'') + path = "/#{prefix}#{path}" unless prefix.blank? segments = segments_for_route_path(path) defaults, requirements, conditions = divide_route_options(segments, options) diff --git a/actionpack/lib/action_controller/routing/recognition_optimisation.rb b/actionpack/lib/action_controller/routing/recognition_optimisation.rb index ebc553512f..9bfebff0c0 100644 --- a/actionpack/lib/action_controller/routing/recognition_optimisation.rb +++ b/actionpack/lib/action_controller/routing/recognition_optimisation.rb @@ -98,7 +98,6 @@ module ActionController if Array === item i += 1 start = (i == 1) - final = (i == list.size) tag, sub = item if tag == :dynamic body += padding + "#{start ? 'if' : 'elsif'} true\n" diff --git a/actionpack/lib/action_controller/routing/segments.rb b/actionpack/lib/action_controller/routing/segments.rb index 129e87c139..4f936d51d2 100644 --- a/actionpack/lib/action_controller/routing/segments.rb +++ b/actionpack/lib/action_controller/routing/segments.rb @@ -3,7 +3,11 @@ module ActionController class Segment #:nodoc: RESERVED_PCHAR = ':@&=+$,;' SAFE_PCHAR = "#{URI::REGEXP::PATTERN::UNRESERVED}#{RESERVED_PCHAR}" - UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + if RUBY_VERSION >= '1.9' + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false).freeze + else + UNSAFE_PCHAR = Regexp.new("[^#{SAFE_PCHAR}]", false, 'N').freeze + end # TODO: Convert :is_optional accessor to read only attr_accessor :is_optional @@ -314,13 +318,17 @@ module ActionController end def regexp_chunk - '(\.[^/?\.]+)?' + '/|(\.[^/?\.]+)?' end def to_s '(.:format)?' end - + + def extract_value + "#{local_name} = options[:#{key}] && options[:#{key}].to_s.downcase" + end + #the value should not include the period (.) def match_extraction(next_capture) %[ diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index dbaec00bee..9dd09c30b4 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -258,11 +258,11 @@ module ActionController #:nodoc: # Returns binary content (downloadable file), converted to a String def binary_content - raise "Response body is not a Proc: #{body.inspect}" unless body.kind_of?(Proc) + raise "Response body is not a Proc: #{body_parts.inspect}" unless body_parts.kind_of?(Proc) require 'stringio' sio = StringIO.new - body.call(self, sio) + body_parts.call(self, sio) sio.rewind sio.read diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index bb6cb437b7..16720b915b 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -68,29 +68,17 @@ module ActionController # This generates, among other things, the method <tt>users_path</tt>. By default, # this method is accessible from your controllers, views and mailers. If you need # to access this auto-generated method from other places (such as a model), then - # you can do that in two ways. - # - # The first way is to include ActionController::UrlWriter in your class: + # you can do that by including ActionController::UrlWriter in your class: # # class User < ActiveRecord::Base - # include ActionController::UrlWriter # !!! + # include ActionController::UrlWriter # - # def name=(value) - # write_attribute('name', value) - # write_attribute('base_uri', users_path) # !!! + # def base_uri + # user_path(self) # end # end # - # The second way is to access them through ActionController::UrlWriter. - # The autogenerated named routes methods are available as class methods: - # - # class User < ActiveRecord::Base - # def name=(value) - # write_attribute('name', value) - # path = ActionController::UrlWriter.users_path # !!! - # write_attribute('base_uri', path) # !!! - # end - # end + # User.find(1).base_uri # => "/users/1" module UrlWriter def self.included(base) #:nodoc: ActionController::Routing::Routes.install_helpers(base) diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb index 376bb87409..e2c49c284f 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/selector.rb @@ -556,7 +556,7 @@ module HTML end # Attribute value. - next if statement.sub!(/^\[\s*([[:alpha:]][\w\-]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match| + next if statement.sub!(/^\[\s*([[:alpha:]][\w\-:]*)\s*((?:[~|^$*])?=)?\s*('[^']*'|"[^*]"|[^\]]*)\s*\]/) do |match| name, equality, value = $1, $2, $3 if value == "?" value = values.shift diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb index 6c03b55552..6349b95094 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack.rb @@ -23,14 +23,16 @@ module Rack # Return the Rack release as a dotted string. def self.release - "0.4" + "1.0 bundled" end autoload :Builder, "rack/builder" autoload :Cascade, "rack/cascade" + autoload :Chunked, "rack/chunked" autoload :CommonLogger, "rack/commonlogger" autoload :ConditionalGet, "rack/conditionalget" autoload :ContentLength, "rack/content_length" + autoload :ContentType, "rack/content_type" autoload :File, "rack/file" autoload :Deflater, "rack/deflater" autoload :Directory, "rack/directory" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb index 8489c9b9c4..214df6299e 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/abstract/handler.rb @@ -8,8 +8,8 @@ module Rack attr_accessor :realm - def initialize(app, &authenticator) - @app, @authenticator = app, authenticator + def initialize(app, realm=nil, &authenticator) + @app, @realm, @authenticator = app, realm, authenticator end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb index 6d2bd29c2e..e579dc9632 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/md5.rb @@ -21,7 +21,7 @@ module Rack attr_writer :passwords_hashed - def initialize(app) + def initialize(*args) super @passwords_hashed = nil end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb index a40f57b7f1..a8aa3bf996 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/auth/digest/request.rb @@ -8,7 +8,7 @@ module Rack class Request < Auth::AbstractRequest def method - @env['REQUEST_METHOD'] + @env['rack.methodoverride.original_method'] || @env['REQUEST_METHOD'] end def digest? diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb index 25994d5a44..295235e56a 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/builder.rb @@ -34,11 +34,7 @@ module Rack end def use(middleware, *args, &block) - @ins << if block_given? - lambda { |app| middleware.new(app, *args, &block) } - else - lambda { |app| middleware.new(app, *args) } - end + @ins << lambda { |app| middleware.new(app, *args, &block) } end def run(app) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb new file mode 100644 index 0000000000..280d89dd65 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/chunked.rb @@ -0,0 +1,49 @@ +require 'rack/utils' + +module Rack + + # Middleware that applies chunked transfer encoding to response bodies + # when the response does not include a Content-Length header. + class Chunked + include Rack::Utils + + def initialize(app) + @app = app + end + + def call(env) + status, headers, body = @app.call(env) + headers = HeaderHash.new(headers) + + if env['HTTP_VERSION'] == 'HTTP/1.0' || + STATUS_WITH_NO_ENTITY_BODY.include?(status) || + headers['Content-Length'] || + headers['Transfer-Encoding'] + [status, headers.to_hash, body] + else + dup.chunk(status, headers, body) + end + end + + def chunk(status, headers, body) + @body = body + headers.delete('Content-Length') + headers['Transfer-Encoding'] = 'chunked' + [status, headers.to_hash, self] + end + + def each + term = "\r\n" + @body.each do |chunk| + size = bytesize(chunk) + next if size == 0 + yield [size.to_s(16), term, chunk, term].join + end + yield ["0", term, "", term].join + end + + def close + @body.close if @body.respond_to?(:close) + end + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb index bce22a32c5..1e56d43853 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_length.rb @@ -3,21 +3,23 @@ require 'rack/utils' module Rack # Sets the Content-Length header on responses with fixed-length bodies. class ContentLength + include Rack::Utils + def initialize(app) @app = app end def call(env) status, headers, body = @app.call(env) - headers = Utils::HeaderHash.new(headers) + headers = HeaderHash.new(headers) - if !Utils::STATUS_WITH_NO_ENTITY_BODY.include?(status) && + if !STATUS_WITH_NO_ENTITY_BODY.include?(status) && !headers['Content-Length'] && !headers['Transfer-Encoding'] && (body.respond_to?(:to_ary) || body.respond_to?(:to_str)) body = [body] if body.respond_to?(:to_str) # rack 0.4 compat - length = body.to_ary.inject(0) { |len, part| len + part.length } + length = body.to_ary.inject(0) { |len, part| len + bytesize(part) } headers['Content-Length'] = length.to_s end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb new file mode 100644 index 0000000000..0c1e1ca3e1 --- /dev/null +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/content_type.rb @@ -0,0 +1,23 @@ +require 'rack/utils' + +module Rack + + # Sets the Content-Type header on responses which don't have one. + # + # Builder Usage: + # use Rack::ContentType, "text/plain" + # + # When no content type argument is provided, "text/html" is assumed. + class ContentType + def initialize(app, content_type = "text/html") + @app, @content_type = app, content_type + end + + def call(env) + status, headers, body = @app.call(env) + headers = Utils::HeaderHash.new(headers) + headers['Content-Type'] ||= @content_type + [status, headers.to_hash, body] + end + end +end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb index 3e66680092..a42b7477ae 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/deflater.rb @@ -36,12 +36,12 @@ module Rack mtime = headers.key?("Last-Modified") ? Time.httpdate(headers["Last-Modified"]) : Time.now body = self.class.gzip(body, mtime) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) headers = headers.merge("Content-Encoding" => "gzip", "Content-Length" => size.to_s) [status, headers, [body]] when "deflate" body = self.class.deflate(body) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) headers = headers.merge("Content-Encoding" => "deflate", "Content-Length" => size.to_s) [status, headers, [body]] when "identity" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb index 56ee5e7b60..acdd3029d3 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/directory.rb @@ -70,7 +70,7 @@ table { width:100%%; } return unless @path_info.include? ".." body = "Forbidden\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) return [403, {"Content-Type" => "text/plain","Content-Length" => size.to_s}, [body]] end @@ -89,6 +89,8 @@ table { width:100%%; } type = stat.directory? ? 'directory' : Mime.mime_type(ext) size = stat.directory? ? '-' : filesize_format(size) mtime = stat.mtime.httpdate + url << '/' if stat.directory? + basename << '/' if stat.directory? @files << [ url, basename, size, type, mtime ] end @@ -120,7 +122,7 @@ table { width:100%%; } def entity_not_found body = "Entity not found: #{@path_info}\n" - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) return [404, {"Content-Type" => "text/plain", "Content-Length" => size.to_s}, [body]] end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb index 7869227a36..fe62bd6b86 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/file.rb @@ -60,7 +60,7 @@ module Rack body = self else body = [F.read(@path)] - size = body.first.size + size = Utils.bytesize(body.first) end [200, { diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb index f2c976cf46..e38156c7f0 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/cgi.rb @@ -1,3 +1,5 @@ +require 'rack/content_length' + module Rack module Handler class CGI @@ -6,6 +8,8 @@ module Rack end def self.serve(app) + app = ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb index f03e1615c9..6324c7d274 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/fastcgi.rb @@ -1,5 +1,6 @@ require 'fcgi' require 'socket' +require 'rack/content_length' module Rack module Handler @@ -29,6 +30,8 @@ module Rack end def self.serve(request, app) + app = Rack::ContentLength.new(app) + env = request.env env.delete "HTTP_CONTENT_LENGTH" diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb index 1f850fc77b..c65ba3ec8e 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/lsws.rb @@ -1,5 +1,6 @@ require 'lsapi' -#require 'cgi' +require 'rack/content_length' + module Rack module Handler class LSWS @@ -9,11 +10,13 @@ module Rack end end def self.serve(app) + app = Rack::ContentLength.new(app) + env = ENV.to_hash env.delete "HTTP_CONTENT_LENGTH" env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/" env.update({"rack.version" => [0,1], - "rack.input" => $stdin, + "rack.input" => StringIO.new($stdin.read.to_s), "rack.errors" => $stderr, "rack.multithread" => false, "rack.multiprocess" => true, diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb index 178a1a8fe4..f0c0d58330 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/mongrel.rb @@ -1,5 +1,7 @@ require 'mongrel' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -33,7 +35,7 @@ module Rack end def initialize(app) - @app = app + @app = Rack::Chunked.new(Rack::ContentLength.new(app)) end def process(request, response) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb index fd18a8359b..9495c66374 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/scgi.rb @@ -1,5 +1,7 @@ require 'scgi' require 'stringio' +require 'rack/content_length' +require 'rack/chunked' module Rack module Handler @@ -14,7 +16,7 @@ module Rack end def initialize(settings = {}) - @app = settings[:app] + @app = Rack::Chunked.new(Rack::ContentLength.new(settings[:app])) @log = Object.new def @log.info(*args); end def @log.error(*args); end diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb index 7ad088b36a..3d4fedff75 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/thin.rb @@ -1,9 +1,12 @@ require "thin" +require "rack/content_length" +require "rack/chunked" module Rack module Handler class Thin def self.run(app, options={}) + app = Rack::Chunked.new(Rack::ContentLength.new(app)) server = ::Thin::Server.new(options[:Host] || '0.0.0.0', options[:Port] || 8080, app) diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb index 40be79de13..829e7d6bf8 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/handler/webrick.rb @@ -1,5 +1,6 @@ require 'webrick' require 'stringio' +require 'rack/content_length' module Rack module Handler @@ -14,7 +15,7 @@ module Rack def initialize(server, app) super server - @app = app + @app = Rack::ContentLength.new(app) end def service(req, res) @@ -35,7 +36,12 @@ module Rack env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"] env["QUERY_STRING"] ||= "" env["REQUEST_PATH"] ||= "/" - env.delete "PATH_INFO" if env["PATH_INFO"] == "" + if env["PATH_INFO"] == "" + env.delete "PATH_INFO" + else + path, n = req.request_uri.path, env["SCRIPT_NAME"].length + env["PATH_INFO"] = path[n, path.length-n] + end status, headers, body = @app.call(env) begin diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb index 7eb05437f0..44a33ce36e 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/lint.rb @@ -88,7 +88,9 @@ module Rack ## within the application. This may be an ## empty string, if the request URL targets ## the application root and does not have a - ## trailing slash. + ## trailing slash. This information should be + ## decoded by the server if it comes from a + ## URL. ## <tt>QUERY_STRING</tt>:: The portion of the request URL that ## follows the <tt>?</tt>, if any. May be @@ -372,59 +374,43 @@ module Rack ## === The Content-Length def check_content_length(status, headers, env) - chunked_response = false - headers.each { |key, value| - if key.downcase == 'transfer-encoding' - chunked_response = value.downcase != 'identity' - end - } - headers.each { |key, value| if key.downcase == 'content-length' - ## There must be a <tt>Content-Length</tt>, except when the - ## +Status+ is 1xx, 204 or 304, in which case there must be none - ## given. + ## There must not be a <tt>Content-Length</tt> header when the + ## +Status+ is 1xx, 204 or 304. assert("Content-Length header found in #{status} response, not allowed") { not Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i } - assert('Content-Length header should not be used if body is chunked') { - not chunked_response - } - bytes = 0 string_body = true - @body.each { |part| - unless part.kind_of?(String) - string_body = false - break - end + if @body.respond_to?(:to_ary) + @body.each { |part| + unless part.kind_of?(String) + string_body = false + break + end - bytes += (part.respond_to?(:bytesize) ? part.bytesize : part.size) - } - - if env["REQUEST_METHOD"] == "HEAD" - assert("Response body was given for HEAD request, but should be empty") { - bytes == 0 + bytes += Rack::Utils.bytesize(part) } - else - if string_body - assert("Content-Length header was #{value}, but should be #{bytes}") { - value == bytes.to_s + + if env["REQUEST_METHOD"] == "HEAD" + assert("Response body was given for HEAD request, but should be empty") { + bytes == 0 } + else + if string_body + assert("Content-Length header was #{value}, but should be #{bytes}") { + value == bytes.to_s + } + end end end return end } - - if [ String, Array ].include?(@body.class) && !chunked_response - assert('No Content-Length header found') { - Rack::Utils::STATUS_WITH_NO_ENTITY_BODY.include? status.to_i - } - end end ## === The Body diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb index a593110139..caf60d5b19 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/response.rb @@ -16,6 +16,8 @@ module Rack # Your application's +call+ should end returning Response#finish. class Response + attr_accessor :length + def initialize(body=[], status=200, header={}, &block) @status = status @header = Utils::HeaderHash.new({"Content-Type" => "text/html"}. diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb index 5f13404dce..28258c7c89 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/showstatus.rb @@ -27,7 +27,7 @@ module Rack message = Rack::Utils::HTTP_STATUS_CODES[status.to_i] || status.to_s detail = env["rack.showstatus.detail"] || message body = @template.result(binding) - size = body.respond_to?(:bytesize) ? body.bytesize : body.size + size = Rack::Utils.bytesize(body) [status, headers.merge("Content-Type" => "text/html", "Content-Length" => size.to_s), [body]] else [status, headers, body] diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb index eb1457a850..0ff32df181 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/urlmap.rb @@ -12,7 +12,11 @@ module Rack # first, since they are most specific. class URLMap - def initialize(map) + def initialize(map = {}) + remap(map) + end + + def remap(map) @mapping = map.map { |location, app| if location =~ %r{\Ahttps?://(.*?)(/.*)} host, location = $1, $2 diff --git a/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb b/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb index f352cb6783..0a61bce707 100644 --- a/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb +++ b/actionpack/lib/action_controller/vendor/rack-1.0/rack/utils.rb @@ -144,6 +144,19 @@ module Rack end module_function :select_best_encoding + # Return the bytesize of String; uses String#length under Ruby 1.8 and + # String#bytesize under 1.9. + if ''.respond_to?(:bytesize) + def bytesize(string) + string.bytesize + end + else + def bytesize(string) + string.size + end + end + module_function :bytesize + # Context allows the use of a compatible middleware at different points # in a request handling stack. A compatible middleware must define # #context which should take the arguments env and app. The first of which @@ -359,7 +372,7 @@ module Rack data = body end - Utils.normalize_params(params, name, data) + Utils.normalize_params(params, name, data) unless data.nil? break if buf.empty? || content_length == -1 } diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb index f20e44a7d5..e0aa2a5f2f 100644 --- a/actionpack/lib/action_pack/version.rb +++ b/actionpack/lib/action_pack/version.rb @@ -2,7 +2,7 @@ module ActionPack #:nodoc: module VERSION #:nodoc: MAJOR = 2 MINOR = 3 - TINY = 0 + TINY = 2 STRING = [MAJOR, MINOR, TINY].join('.') end diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 65b2062337..9c0134e7f7 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -183,12 +183,12 @@ module ActionView #:nodoc: cattr_accessor :debug_rjs # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. - # Automaticaly reloading templates are not thread safe and should only be used in development mode. - @@cache_template_loading = false + # Automatically reloading templates are not thread safe and should only be used in development mode. + @@cache_template_loading = nil cattr_accessor :cache_template_loading def self.cache_template_loading? - ActionController::Base.allow_concurrency || cache_template_loading + ActionController::Base.allow_concurrency || (cache_template_loading.nil? ? !ActiveSupport::Dependencies.load? : cache_template_loading) end attr_internal :request @@ -221,10 +221,12 @@ module ActionView #:nodoc: def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: @assigns = assigns_for_first_render @assigns_added = nil - @_render_stack = [] @controller = controller @helpers = ProxyModule.new(self) self.view_paths = view_paths + + @_first_render = nil + @_current_render = nil end attr_reader :view_paths @@ -286,7 +288,25 @@ module ActionView #:nodoc: # Access the current template being rendered. # Returns a ActionView::Template object. def template - @_render_stack.last + @_current_render + end + + def template=(template) #:nodoc: + @_first_render ||= template + @_current_render = template + end + + def with_template(current_template) + last_template, self.template = template, current_template + yield + ensure + self.template = last_template + end + + def punctuate_body!(part) + flush_output_buffer + response.body_parts << part + nil end private diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index 8b56d241ae..541899ea6a 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -121,7 +121,7 @@ module ActionView if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) content_tag("div", - "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}", + "#{options[:prepend_text]}#{ERB::Util.html_escape(errors.is_a?(Array) ? errors.first : errors)}#{options[:append_text]}", :class => options[:css_class] ) else @@ -198,7 +198,7 @@ module ActionView locale.t :header, :count => count, :model => object_name end message = options.include?(:message) ? options[:message] : locale.t(:body) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join + error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, ERB::Util.html_escape(msg)) } }.join contents = '' contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index e86ca27f31..9e39536653 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -131,6 +131,14 @@ module ActionView ensure self.output_buffer = old_buffer end + + # Add the output buffer to the response body and start a new one. + def flush_output_buffer #:nodoc: + if output_buffer && output_buffer != '' + response.body_parts << output_buffer + self.output_buffer = '' + end + end end end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index b7ef1fb90d..c74909a360 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -876,8 +876,8 @@ module ActionView input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') end - # Given an ordering of datetime components, create the selection html - # and join them with their appropriate seperators + # Given an ordering of datetime components, create the selection HTML + # and join them with their appropriate separators. def build_selects_from_types(order) select = '' order.reverse.each do |type| diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 4fef2b443e..a59829b23f 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -5,17 +5,24 @@ require 'action_view/helpers/form_tag_helper' module ActionView module Helpers - # Form helpers are designed to make working with models much easier compared to using just standard HTML - # elements by providing a set of methods for creating forms based on your models. This helper generates the HTML - # for forms, providing a method for each sort of input (e.g., text, password, select, and so on). When the form - # is submitted (i.e., when the user hits the submit button or <tt>form.submit</tt> is called via JavaScript), the form inputs will be bundled into the <tt>params</tt> object and passed back to the controller. + # Form helpers are designed to make working with models much easier + # compared to using just standard HTML elements by providing a set of + # methods for creating forms based on your models. This helper generates + # the HTML for forms, providing a method for each sort of input + # (e.g., text, password, select, and so on). When the form is submitted + # (i.e., when the user hits the submit button or <tt>form.submit</tt> is + # called via JavaScript), the form inputs will be bundled into the + # <tt>params</tt> object and passed back to the controller. # - # There are two types of form helpers: those that specifically work with model attributes and those that don't. - # This helper deals with those that work with model attributes; to see an example of form helpers that don't work - # with model attributes, check the ActionView::Helpers::FormTagHelper documentation. + # There are two types of form helpers: those that specifically work with + # model attributes and those that don't. This helper deals with those that + # work with model attributes; to see an example of form helpers that don't + # work with model attributes, check the ActionView::Helpers::FormTagHelper + # documentation. # - # The core method of this helper, form_for, gives you the ability to create a form for a model instance; - # for example, let's say that you have a model <tt>Person</tt> and want to create a new instance of it: + # The core method of this helper, form_for, gives you the ability to create + # a form for a model instance; for example, let's say that you have a model + # <tt>Person</tt> and want to create a new instance of it: # # # Note: a @person variable will have been created in the controller. # # For example: @person = Person.new @@ -40,17 +47,22 @@ module ActionView # <%= submit_tag 'Create' %> # <% end %> # - # This example will render the <tt>people/_form</tt> partial, setting a local variable called <tt>form</tt> which references the yielded FormBuilder. - # - # The <tt>params</tt> object created when this form is submitted would look like: + # This example will render the <tt>people/_form</tt> partial, setting a + # local variable called <tt>form</tt> which references the yielded + # FormBuilder. The <tt>params</tt> object created when this form is + # submitted would look like: # # {"action"=>"create", "controller"=>"persons", "person"=>{"first_name"=>"William", "last_name"=>"Smith"}} # - # The params hash has a nested <tt>person</tt> value, which can therefore be accessed with <tt>params[:person]</tt> in the controller. - # If were editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than <tt>Person.new</tt> in the controller), the objects - # attribute values are filled into the form (e.g., the <tt>person_first_name</tt> field would have that person's first name in it). + # The params hash has a nested <tt>person</tt> value, which can therefore + # be accessed with <tt>params[:person]</tt> in the controller. If were + # editing/updating an instance (e.g., <tt>Person.find(1)</tt> rather than + # <tt>Person.new</tt> in the controller), the objects attribute values are + # filled into the form (e.g., the <tt>person_first_name</tt> field would + # have that person's first name in it). # - # If the object name contains square brackets the id for the object will be inserted. For example: + # If the object name contains square brackets the id for the object will be + # inserted. For example: # # <%= text_field "person[]", "name" %> # @@ -58,8 +70,10 @@ module ActionView # # <input type="text" id="person_<%= @person.id %>_name" name="person[<%= @person.id %>][name]" value="<%= @person.name %>" /> # - # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial - # used by <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may come in handy. Example: + # If the helper is being used to generate a repetitive sequence of similar + # form elements, for example in a partial used by + # <tt>render_collection_of_partials</tt>, the <tt>index</tt> option may + # come in handy. Example: # # <%= text_field "person", "name", "index" => 1 %> # @@ -67,14 +81,17 @@ module ActionView # # <input type="text" id="person_1_name" name="person[1][name]" value="<%= @person.name %>" /> # - # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and <tt>fields_for</tt>. This automatically applies - # the <tt>index</tt> to all the nested fields. + # An <tt>index</tt> option may also be passed to <tt>form_for</tt> and + # <tt>fields_for</tt>. This automatically applies the <tt>index</tt> to + # all the nested fields. # - # There are also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html, - # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html + # There are also methods for helping to build form tags in + # link:classes/ActionView/Helpers/FormOptionsHelper.html, + # link:classes/ActionView/Helpers/DateHelper.html, and + # link:classes/ActionView/Helpers/ActiveRecordHelper.html module FormHelper - # Creates a form and a scope around a specific model object that is used as - # a base for questioning about values for the fields. + # Creates a form and a scope around a specific model object that is used + # as a base for questioning about values for the fields. # # Rails provides succinct resource-oriented form generation with +form_for+ # like this: @@ -86,13 +103,15 @@ module ActionView # <%= f.text_field :author %><br /> # <% end %> # - # There, +form_for+ is able to generate the rest of RESTful form parameters - # based on introspection on the record, but to understand what it does we - # need to dig first into the alternative generic usage it is based upon. + # There, +form_for+ is able to generate the rest of RESTful form + # parameters based on introspection on the record, but to understand what + # it does we need to dig first into the alternative generic usage it is + # based upon. # # === Generic form_for # - # The generic way to call +form_for+ yields a form builder around a model: + # The generic way to call +form_for+ yields a form builder around a + # model: # # <% form_for :person, :url => { :action => "update" } do |f| %> # <%= f.error_messages %> @@ -103,8 +122,8 @@ module ActionView # <% end %> # # There, the first argument is a symbol or string with the name of the - # object the form is about, and also the name of the instance variable the - # object is stored in. + # object the form is about, and also the name of the instance variable + # the object is stored in. # # The form builder acts as a regular form helper that somehow carries the # model. Thus, the idea is that @@ -137,17 +156,18 @@ module ActionView # In any of its variants, the rightmost argument to +form_for+ is an # optional hash of options: # - # * <tt>:url</tt> - The URL the form is submitted to. It takes the same fields - # you pass to +url_for+ or +link_to+. In particular you may pass here a - # named route directly as well. Defaults to the current action. + # * <tt>:url</tt> - The URL the form is submitted to. It takes the same + # fields you pass to +url_for+ or +link_to+. In particular you may pass + # here a named route directly as well. Defaults to the current action. # * <tt>:html</tt> - Optional HTML attributes for the form tag. # - # Worth noting is that the +form_for+ tag is called in a ERb evaluation block, - # not an ERb output block. So that's <tt><% %></tt>, not <tt><%= %></tt>. + # Worth noting is that the +form_for+ tag is called in a ERb evaluation + # block, not an ERb output block. So that's <tt><% %></tt>, not + # <tt><%= %></tt>. # # Also note that +form_for+ doesn't create an exclusive scope. It's still - # possible to use both the stand-alone FormHelper methods and methods from - # FormTagHelper. For example: + # possible to use both the stand-alone FormHelper methods and methods + # from FormTagHelper. For example: # # <% form_for :person, @person, :url => { :action => "update" } do |f| %> # First name: <%= f.text_field :first_name %> @@ -156,16 +176,16 @@ module ActionView # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %> # <% end %> # - # This also works for the methods in FormOptionHelper and DateHelper that are - # designed to work with an object as base, like FormOptionHelper#collection_select - # and DateHelper#datetime_select. + # This also works for the methods in FormOptionHelper and DateHelper that + # are designed to work with an object as base, like + # FormOptionHelper#collection_select and DateHelper#datetime_select. # # === Resource-oriented style # - # As we said above, in addition to manually configuring the +form_for+ call, - # you can rely on automated resource identification, which will use the conventions - # and named routes of that approach. This is the preferred way to use +form_for+ - # nowadays. + # As we said above, in addition to manually configuring the +form_for+ + # call, you can rely on automated resource identification, which will use + # the conventions and named routes of that approach. This is the + # preferred way to use +form_for+ nowadays. # # For example, if <tt>@post</tt> is an existing record you want to edit # @@ -205,8 +225,10 @@ module ActionView # # === Customized form builders # - # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers, - # then use your custom builder. For example, let's say you made a helper to automatically add labels to form inputs. + # You can also build forms using a customized FormBuilder class. Subclass + # FormBuilder and override or define some more helpers, then use your + # custom builder. For example, let's say you made a helper to + # automatically add labels to form inputs. # # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> @@ -219,16 +241,23 @@ module ActionView # # <%= render :partial => f %> # - # The rendered template is <tt>people/_labelling_form</tt> and the local variable referencing the form builder is called <tt>labelling_form</tt>. + # The rendered template is <tt>people/_labelling_form</tt> and the local + # variable referencing the form builder is called + # <tt>labelling_form</tt>. # - # In many cases you will want to wrap the above in another helper, so you could do something like the following: + # The custom FormBuilder class is automatically merged with the options + # of a nested fields_for call, unless it's explicitely set. + # + # In many cases you will want to wrap the above in another helper, so you + # could do something like the following: # # def labelled_form_for(record_or_name_or_array, *args, &proc) # options = args.extract_options! # form_for(record_or_name_or_array, *(args << options.merge(:builder => LabellingFormBuilder)), &proc) # end # - # If you don't need to attach a form to a model instance, then check out FormTagHelper#form_tag. + # If you don't need to attach a form to a model instance, then check out + # FormTagHelper#form_tag. def form_for(record_or_name_or_array, *args, &proc) raise ArgumentError, "Missing block" unless block_given? @@ -599,7 +628,7 @@ module ActionView # # The HTML specification says unchecked check boxes are not successful, and # thus web browsers do not send them. Unfortunately this introduces a gotcha: - # if an Invoice model has a +paid+ flag, and in the form that edits a paid + # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # @@ -607,12 +636,15 @@ module ActionView # # wouldn't update the flag. # - # To prevent this the helper generates a hidden field with the same name as - # the checkbox after the very check box. So, the client either sends only the - # hidden field (representing the check box is unchecked), or both fields. - # Since the HTML specification says key/value pairs have to be sent in the - # same order they appear in the form and Rails parameters extraction always - # gets the first occurrence of any given key, that works in ordinary forms. + # To prevent this the helper generates an auxiliary hidden field before + # the very check box. The hidden field has the same name and its + # attributes mimick an unchecked check box. + # + # This way, the client either sends only the hidden field (representing + # the check box is unchecked), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. # # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in @@ -623,22 +655,26 @@ module ActionView # <% end %> # # because parameter name repetition is precisely what Rails seeks to distinguish - # the elements of the array. + # the elements of the array. For each item with a checked check box you + # get an extra ghost item with only that attribute, assigned to "0". + # + # In that case it is preferable to either use +check_box_tag+ or to use + # hashes instead of arrays. # # ==== Examples # # Let's say that @post.validated? is 1: # check_box("post", "validated") - # # => <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> - # # <input name="post[validated]" type="hidden" value="0" /> + # # => <input name="post[validated]" type="hidden" value="0" /> + # # <input type="checkbox" id="post_validated" name="post[validated]" value="1" /> # # # Let's say that @puppy.gooddog is "no": # check_box("puppy", "gooddog", {}, "yes", "no") - # # => <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> - # # <input name="puppy[gooddog]" type="hidden" value="no" /> + # # => <input name="puppy[gooddog]" type="hidden" value="no" /> + # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> # # check_box("eula", "accepted", { :class => 'eula_check' }, "yes", "no") - # # => <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> - # # <input name="eula[accepted]" type="hidden" value="no" /> + # # => <input name="eula[accepted]" type="hidden" value="no" /> + # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> # def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0") InstanceTag.new(object_name, method, self, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value) @@ -910,6 +946,11 @@ module ActionView index = "" end + if options[:builder] + args << {} unless args.last.is_a?(Hash) + args.last[:builder] ||= options[:builder] + end + case record_or_name_or_array when String, Symbol if nested_attributes_association?(record_or_name_or_array) diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 4646bc118b..6d39a53adc 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -360,8 +360,8 @@ module ActionView end if confirm = options.delete("confirm") - options["onclick"] ||= '' - options["onclick"] << "return #{confirm_javascript_function(confirm)};" + options["onclick"] ||= 'return true;' + options["onclick"] = "if (!#{confirm_javascript_function(confirm)}) return false; #{options['onclick']}" end tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options.stringify_keys) diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index e622f97b9e..dea958deaf 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -15,6 +15,7 @@ module ActionView # * <tt>:country_code</tt> - Sets the country code for the phone number. # # ==== Examples + # number_to_phone(5551234) # => 555-1234 # number_to_phone(1235551234) # => 123-555-1234 # number_to_phone(1235551234, :area_code => true) # => (123) 555-1234 # number_to_phone(1235551234, :delimiter => " ") # => 123 555 1234 @@ -37,7 +38,8 @@ module ActionView str << if area_code number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4}$)/,"(\\1) \\2#{delimiter}\\3") else - number.gsub!(/([0-9]{1,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.gsub!(/([0-9]{0,3})([0-9]{3})([0-9]{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3") + number.starts_with?('-') ? number.slice!(1..-1) : number end str << " x #{extension}" unless extension.blank? str @@ -138,7 +140,7 @@ module ActionView # number_with_delimiter(12345678) # => 12,345,678 # number_with_delimiter(12345678.05) # => 12,345,678.05 # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 - # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 + # number_with_delimiter(12345678, :separator => ",") # => 12,345,678 # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 # diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 18a209dcea..91ef72e54b 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -107,7 +107,7 @@ module ActionView # on the page in an Ajax response. module PrototypeHelper unless const_defined? :CALLBACKS - CALLBACKS = Set.new([ :uninitialized, :loading, :loaded, + CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success ] + (100..599).to_a) AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 63fe0c1c57..573b99b96e 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -324,7 +324,7 @@ module ActionView # Turns all URLs and e-mail addresses into clickable links. The <tt>:link</tt> option # will limit what should be linked. You can add HTML attributes to the links using - # <tt>:href_options</tt>. Possible values for <tt>:link</tt> are <tt>:all</tt> (default), + # <tt>:html</tt>. Possible values for <tt>:link</tt> are <tt>:all</tt> (default), # <tt>:email_addresses</tt>, and <tt>:urls</tt>. If a block is given, each URL and # e-mail address is yielded and the result is used as the link text. # @@ -341,7 +341,7 @@ module ActionView # # => "Visit http://www.loudthinking.com/ or e-mail <a href=\"mailto:david@loudthinking.com\">david@loudthinking.com</a>" # # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :href_options => { :target => '_blank' }) do |text| + # auto_link(post_body, :html => { :target => '_blank' }) do |text| # truncate(text, 15) # end # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. @@ -359,7 +359,7 @@ module ActionView # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." - def auto_link(text, *args, &block)#link = :all, href_options = {}, &block) + def auto_link(text, *args, &block)#link = :all, html = {}, &block) return '' if text.blank? options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter @@ -536,8 +536,9 @@ module ActionView text.gsub(AUTO_LINK_RE) do href = $& punctuation = '' - # detect already linked URLs - if $` =~ /<a\s[^>]*href="$/ + left, right = $`, $' + # detect already linked URLs and URLs in the middle of a tag + if left =~ /<[^>]+$/ && right =~ /^[^>]*>/ # do not change string; URL is alreay linked href else diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index 41f9f486e5..8cc3fe291c 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -40,7 +40,7 @@ module ActionView #:nodoc: each(&:load!) end - def find_template(original_template_path, format = nil) + def find_template(original_template_path, format = nil, html_fallback = true) return original_template_path if original_template_path.respond_to?(:render) template_path = original_template_path.sub(/^\//, '') @@ -54,14 +54,14 @@ module ActionView #:nodoc: elsif template = load_path[template_path] return template # Try to find html version if the format is javascript - elsif format == :js && template = load_path["#{template_path}.#{I18n.locale}.html"] + elsif format == :js && html_fallback && template = load_path["#{template_path}.#{I18n.locale}.html"] return template - elsif format == :js && template = load_path["#{template_path}.html"] + elsif format == :js && html_fallback && template = load_path["#{template_path}.html"] return template end end - return Template.new(original_template_path, original_template_path =~ /\A\// ? "" : ".") if File.file?(original_template_path) + return Template.new(original_template_path, original_template_path.to_s =~ /\A\// ? "" : ".") if File.file?(original_template_path) raise MissingTemplate.new(self, original_template_path, format) end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 41080ed629..ff7bc7d9de 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -27,23 +27,19 @@ module ActionView def render(view, local_assigns = {}) compile(local_assigns) - stack = view.instance_variable_get(:@_render_stack) - stack.push(self) - - view.send(:_evaluate_assigns_and_ivars) - view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) - - result = view.send(method_name(local_assigns), local_assigns) do |*names| - ivar = :@_proc_for_layout - if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) - view.capture(*names, &proc) - elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") - view.instance_variable_get(ivar) + view.with_template self do + view.send(:_evaluate_assigns_and_ivars) + view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type) + + view.send(method_name(local_assigns), local_assigns) do |*names| + ivar = :@_proc_for_layout + if !view.instance_variable_defined?(:"@content_for_#{names.first}") && view.instance_variable_defined?(ivar) && (proc = view.instance_variable_get(ivar)) + view.capture(*names, &proc) + elsif view.instance_variable_defined?(ivar = :"@content_for_#{names.first || :layout}") + view.instance_variable_get(ivar) + end end end - - stack.pop - result end def method_name(local_assigns) diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index ea838b9b02..c339c8a554 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -103,12 +103,12 @@ module ActionView #:nodoc: @@exempt_from_layout.merge(regexps) end - attr_accessor :filename, :load_path, :base_path + attr_accessor :template_path, :filename, :load_path, :base_path attr_accessor :locale, :name, :format, :extension delegate :to_s, :to => :path def initialize(template_path, load_path) - template_path = template_path.dup + @template_path = template_path.dup @load_path, @filename = load_path, File.join(load_path, template_path) @base_path, @name, @locale, @format, @extension = split(template_path) @base_path.to_s.gsub!(/\/$/, '') # Push to split method @@ -119,13 +119,20 @@ module ActionView #:nodoc: 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}" + + if valid_extension?(extension) + 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 + else + # template without explicit template handler should only be reachable through its exact path + paths << template_path end + paths end @@ -211,7 +218,7 @@ module ActionView #:nodoc: # Returns file split into an array # [base_path, name, locale, format, extension] def split(file) - if m = file.match(/^(.*\/)?([^\.]+)\.(.*)$/) + if m = file.to_s.match(/^(.*\/)?([^\.]+)\.(.*)$/) base_path = m[1] name = m[2] extensions = m[3] diff --git a/actionpack/test/activerecord/active_record_store_test.rb b/actionpack/test/activerecord/active_record_store_test.rb index 6a75e6050d..c98892edc1 100644 --- a/actionpack/test/activerecord/active_record_store_test.rb +++ b/actionpack/test/activerecord/active_record_store_test.rb @@ -21,6 +21,18 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest render :text => "foo: #{session[:foo].inspect}" end + def get_session_id + session[:foo] + render :text => "#{request.session_options[:id]}" + end + + def call_reset_session + session[:bar] + reset_session + session[:bar] = "baz" + head :ok + end + def rescue_action(e) raise end end @@ -61,6 +73,40 @@ class ActiveRecordStoreTest < ActionController::IntegrationTest end end + def test_setting_session_value_after_session_reset + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/call_reset_session' + assert_response :success + assert_not_equal [], headers['Set-Cookie'] + + get '/get_session_value' + assert_response :success + assert_equal 'foo: nil', response.body + + get '/get_session_id' + assert_response :success + assert_not_equal session_id, response.body + end + end + + def test_getting_session_id + with_test_route_set do + get '/set_session_value' + assert_response :success + assert cookies['_session_id'] + session_id = cookies['_session_id'] + + get '/get_session_id' + assert_response :success + assert_equal session_id, response.body + end + end + def test_prevents_session_fixation with_test_route_set do get '/set_session_value' diff --git a/actionpack/test/controller/assert_select_test.rb b/actionpack/test/controller/assert_select_test.rb index 99c57c0c91..298c7e4db3 100644 --- a/actionpack/test/controller/assert_select_test.rb +++ b/actionpack/test/controller/assert_select_test.rb @@ -76,7 +76,7 @@ class AssertSelectTest < ActionController::TestCase end def assert_failure(message, &block) - e = assert_raises(Assertion, &block) + e = assert_raise(Assertion, &block) assert_match(message, e.message) if Regexp === message assert_equal(message, e.message) if String === message end @@ -95,24 +95,24 @@ class AssertSelectTest < ActionController::TestCase def test_equality_true_false render_html %Q{<div id="1"></div><div id="2"></div>} assert_nothing_raised { assert_select "div" } - assert_raises(Assertion) { assert_select "p" } + assert_raise(Assertion) { assert_select "p" } assert_nothing_raised { assert_select "div", true } - assert_raises(Assertion) { assert_select "p", true } - assert_raises(Assertion) { assert_select "div", false } + assert_raise(Assertion) { assert_select "p", true } + assert_raise(Assertion) { assert_select "div", false } assert_nothing_raised { assert_select "p", false } end def test_equality_string_and_regexp render_html %Q{<div id="1">foo</div><div id="2">foo</div>} assert_nothing_raised { assert_select "div", "foo" } - assert_raises(Assertion) { assert_select "div", "bar" } + assert_raise(Assertion) { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", :text=>"foo" } - assert_raises(Assertion) { assert_select "div", :text=>"bar" } + assert_raise(Assertion) { assert_select "div", :text=>"bar" } assert_nothing_raised { assert_select "div", /(foo|bar)/ } - assert_raises(Assertion) { assert_select "div", /foobar/ } + assert_raise(Assertion) { assert_select "div", /foobar/ } assert_nothing_raised { assert_select "div", :text=>/(foo|bar)/ } - assert_raises(Assertion) { assert_select "div", :text=>/foobar/ } - assert_raises(Assertion) { assert_select "p", :text=>/foobar/ } + assert_raise(Assertion) { assert_select "div", :text=>/foobar/ } + assert_raise(Assertion) { assert_select "p", :text=>/foobar/ } end def test_equality_of_html @@ -120,17 +120,17 @@ class AssertSelectTest < ActionController::TestCase text = "\"This is not a big problem,\" he said." html = "<em>\"This is <strong>not</strong> a big problem,\"</em> he said." assert_nothing_raised { assert_select "p", text } - assert_raises(Assertion) { assert_select "p", html } + assert_raise(Assertion) { assert_select "p", html } assert_nothing_raised { assert_select "p", :html=>html } - assert_raises(Assertion) { assert_select "p", :html=>text } + assert_raise(Assertion) { assert_select "p", :html=>text } # No stripping for pre. render_html %Q{<pre>\n<em>"This is <strong>not</strong> a big problem,"</em> he said.\n</pre>} text = "\n\"This is not a big problem,\" he said.\n" html = "\n<em>\"This is <strong>not</strong> a big problem,\"</em> he said.\n" assert_nothing_raised { assert_select "pre", text } - assert_raises(Assertion) { assert_select "pre", html } + assert_raise(Assertion) { assert_select "pre", html } assert_nothing_raised { assert_select "pre", :html=>html } - assert_raises(Assertion) { assert_select "pre", :html=>text } + assert_raise(Assertion) { assert_select "pre", :html=>text } end def test_counts @@ -210,12 +210,12 @@ class AssertSelectTest < ActionController::TestCase assert_nothing_raised { assert_select "div", "bar" } assert_nothing_raised { assert_select "div", /\w*/ } assert_nothing_raised { assert_select "div", /\w*/, :count=>2 } - assert_raises(Assertion) { assert_select "div", :text=>"foo", :count=>2 } + assert_raise(Assertion) { assert_select "div", :text=>"foo", :count=>2 } assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } assert_nothing_raised { assert_select "div", :html=>"<span>bar</span>" } assert_nothing_raised { assert_select "div", :html=>/\w*/ } assert_nothing_raised { assert_select "div", :html=>/\w*/, :count=>2 } - assert_raises(Assertion) { assert_select "div", :html=>"<span>foo</span>", :count=>2 } + assert_raise(Assertion) { assert_select "div", :html=>"<span>foo</span>", :count=>2 } end end @@ -253,7 +253,12 @@ class AssertSelectTest < ActionController::TestCase 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"} + assert_raise(Assertion) {assert_select_rjs :insert, :top, "test2"} + end + + def test_elect_with_xml_namespace_attributes + render_html %Q{<link xlink:href="http://nowhere.com"></link>} + assert_nothing_raised { assert_select "link[xlink:href=http://nowhere.com]" } end # @@ -331,7 +336,7 @@ class AssertSelectTest < ActionController::TestCase # Test that we fail if there is nothing to pick. def test_assert_select_rjs_fails_if_nothing_to_pick render_rjs { } - assert_raises(Assertion) { assert_select_rjs } + assert_raise(Assertion) { assert_select_rjs } end def test_assert_select_rjs_with_unicode @@ -346,10 +351,10 @@ class AssertSelectTest < ActionController::TestCase if str.respond_to?(:force_encoding) str.force_encoding(Encoding::UTF_8) assert_select str, /\343\203\201..\343\203\210/u - assert_raises(Assertion) { assert_select str, /\343\203\201.\343\203\210/u } + assert_raise(Assertion) { assert_select str, /\343\203\201.\343\203\210/u } else assert_select str, Regexp.new("\343\203\201..\343\203\210",0,'U') - assert_raises(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } + assert_raise(Assertion) { assert_select str, Regexp.new("\343\203\201.\343\203\210",0,'U') } end end end @@ -373,7 +378,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(Assertion) { assert_select_rjs "test4" } + assert_raise(Assertion) { assert_select_rjs "test4" } end def test_assert_select_rjs_for_replace @@ -391,7 +396,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(Assertion) { assert_select_rjs :replace, "test2" } + assert_raise(Assertion) { assert_select_rjs :replace, "test2" } # Replace HTML. assert_select_rjs :replace_html do assert_select "div", 1 @@ -401,7 +406,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" } end def test_assert_select_rjs_for_chained_replace @@ -419,7 +424,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#1" end - assert_raises(Assertion) { assert_select_rjs :chained_replace, "test2" } + assert_raise(Assertion) { assert_select_rjs :chained_replace, "test2" } # Replace HTML. assert_select_rjs :chained_replace_html do assert_select "div", 1 @@ -429,7 +434,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#2" end - assert_raises(Assertion) { assert_select_rjs :replace_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :replace_html, "test1" } end # Simple remove @@ -575,7 +580,7 @@ class AssertSelectTest < ActionController::TestCase assert_select "div", 1 assert_select "#3" end - assert_raises(Assertion) { assert_select_rjs :insert_html, "test1" } + assert_raise(Assertion) { assert_select_rjs :insert_html, "test1" } end # Positioned insert. @@ -608,8 +613,8 @@ class AssertSelectTest < ActionController::TestCase end def test_assert_select_rjs_raise_errors - assert_raises(ArgumentError) { assert_select_rjs(:destroy) } - assert_raises(ArgumentError) { assert_select_rjs(:insert, :left) } + assert_raise(ArgumentError) { assert_select_rjs(:destroy) } + assert_raise(ArgumentError) { assert_select_rjs(:insert, :left) } end # Simple selection from a single result. @@ -701,7 +706,7 @@ EOF # def test_assert_select_email - assert_raises(Assertion) { assert_select_email {} } + assert_raise(Assertion) { assert_select_email {} } AssertSelectMailer.deliver_test "<div><p>foo</p><p>bar</p></div>" assert_select_email do assert_select "div:root" do diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 9af1ccc740..86dafd9221 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -428,6 +428,20 @@ class ActionCacheTest < ActionController::TestCase assert_equal 'application/xml', @response.content_type end + def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key + # run it twice to cache it the first time + get :show, :format => 'xml' + get :show, :format => 'xml' + assert_equal 'application/xml', @response.content_type + end + + def test_correct_content_type_is_returned_for_cache_hit_on_action_with_string_key_from_proc + # run it twice to cache it the first time + get :edit, :id => 1, :format => 'xml' + get :edit, :id => 1, :format => 'xml' + assert_equal 'application/xml', @response.content_type + end + def test_empty_path_is_normalized @mock_controller.mock_url_for = 'http://example.org/' @mock_controller.mock_path = '/' diff --git a/actionpack/test/controller/fake_models.rb b/actionpack/test/controller/fake_models.rb index 7420579ed8..0b30c79b10 100644 --- a/actionpack/test/controller/fake_models.rb +++ b/actionpack/test/controller/fake_models.rb @@ -9,3 +9,11 @@ end class GoodCustomer < Customer end + +module Quiz + class Question < Struct.new(:name, :id) + def to_param + id.to_s + end + end +end diff --git a/actionpack/test/controller/html-scanner/document_test.rb b/actionpack/test/controller/html-scanner/document_test.rb index 1c3facb9e3..c68f04fa75 100644 --- a/actionpack/test/controller/html-scanner/document_test.rb +++ b/actionpack/test/controller/html-scanner/document_test.rb @@ -134,7 +134,7 @@ HTML end def test_invalid_document_raises_exception_when_strict - assert_raises RuntimeError do + assert_raise RuntimeError do doc = HTML::Document.new("<html> <table> <tr> diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index 4913e7633b..00789eea38 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -5,7 +5,8 @@ class HttpDigestAuthenticationTest < ActionController::TestCase before_filter :authenticate, :only => :index before_filter :authenticate_with_request, :only => :display - USERS = { 'lifo' => 'world', 'pretty' => 'please' } + USERS = { 'lifo' => 'world', 'pretty' => 'please', + 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} def index render :text => "Hello Secret" @@ -107,8 +108,42 @@ class HttpDigestAuthenticationTest < ActionController::TestCase assert_equal 'Definitely Maybe', @response.body end - test "authentication request with relative URI" do - @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "/", :username => 'pretty', :password => 'please') + test "authentication request with valid credential and nil session" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + + # session_id = "" in functional test, but is +nil+ in real life + @request.session.session_id = nil + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with request-uri that doesn't match credentials digest-uri" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "/http_digest_authentication_test/dummy_digest/altered/uri" + get :display + + assert_response :unauthorized + assert_equal "Authentication Failed", @response.body + end + + test "authentication request with absolute uri" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:uri => "http://test.host/http_digest_authentication_test/dummy_digest/display", + :username => 'pretty', :password => 'please') + @request.env['REQUEST_URI'] = "http://test.host/http_digest_authentication_test/dummy_digest/display" + get :display + + assert_response :success + assert assigns(:logged_in) + assert_equal 'Definitely Maybe', @response.body + end + + test "authentication request with password stored as ha1 digest hash" do + @request.env['HTTP_AUTHORIZATION'] = encode_credentials(:username => 'dhh', + :password => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":")), + :password_is_ha1 => true) get :display assert_response :success @@ -119,18 +154,22 @@ class HttpDigestAuthenticationTest < ActionController::TestCase private def encode_credentials(options) - options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b") + options.reverse_merge!(:nc => "00000001", :cnonce => "0a4f113b", :password_is_ha1 => false) password = options.delete(:password) - # Perform unautheticated get to retrieve digest parameters to use on subsequent request + # Set in /initializers/session_store.rb. Used as secret in generating nonce + # to prevent tampering of timestamp + ActionController::Base.session_options[:secret] = "session_options_secret" + + # Perform unauthenticated GET to retrieve digest parameters to use on subsequent request get :index assert_response :unauthorized credentials = decode_credentials(@response.headers['WWW-Authenticate']) credentials.merge!(options) - credentials.reverse_merge!(:uri => "http://#{@request.host}#{@request.env['REQUEST_URI']}") - ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password) + credentials.reverse_merge!(:uri => "#{@request.env['REQUEST_URI']}") + ActionController::HttpAuthentication::Digest.encode_credentials("GET", credentials, password, options[:password_is_ha1]) end def decode_credentials(header) diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index b3f40fbe95..e39a934c24 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' class SessionTest < Test::Unit::TestCase StubApp = lambda { |env| - [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, "Hello, World!"] + [200, {"Content-Type" => "text/html", "Content-Length" => "13"}, ["Hello, World!"]] } def setup @@ -389,9 +389,9 @@ class MetalTest < ActionController::IntegrationTest class Poller def self.call(env) if env["PATH_INFO"] =~ /^\/success/ - [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, "Hello World!"] + [200, {"Content-Type" => "text/plain", "Content-Length" => "12"}, ["Hello World!"]] else - [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, ''] + [404, {"Content-Type" => "text/plain", "Content-Length" => "0"}, []] end end end diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index 28555ee3d1..1575674e18 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -79,6 +79,10 @@ end class DefaultLayoutController < LayoutTest end +class AbsolutePathLayoutController < LayoutTest + layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test.rhtml') +end + class HasOwnLayoutController < LayoutTest layout 'item' end @@ -137,12 +141,18 @@ class LayoutSetInResponseTest < ActionController::TestCase ensure ActionController::Base.exempt_from_layout.delete(/\.rhtml$/) end - + def test_layout_is_picked_from_the_controller_instances_view_path @controller = PrependsViewPathController.new get :hello assert_equal 'layouts/alt', @response.layout end + + def test_absolute_pathed_layout + @controller = AbsolutePathLayoutController.new + get :hello + assert_equal "layout_test.rhtml hello.rhtml", @response.body.strip + end end class RenderWithTemplateOptionController < LayoutTest diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index dc59180a68..edd7162325 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -469,7 +469,7 @@ class MimeControllerTest < ActionController::TestCase assert_equal '<html><div id="html_missing">Hello future from Firefox!</div></html>', @response.body @request.accept = "text/iphone" - assert_raises(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } + assert_raise(ActionView::MissingTemplate) { get :iphone_with_html_response_type_without_layout } end end diff --git a/actionpack/test/controller/polymorphic_routes_test.rb b/actionpack/test/controller/polymorphic_routes_test.rb index 53295522ae..146d703619 100644 --- a/actionpack/test/controller/polymorphic_routes_test.rb +++ b/actionpack/test/controller/polymorphic_routes_test.rb @@ -18,6 +18,20 @@ class Tag < Article def response_id; 1 end end +class Tax + attr_reader :id + def save; @id = 1 end + def new_record?; @id.nil? end + def name + model = self.class.name.downcase + @id.nil? ? "new #{model}" : "#{model} ##{@id}" + end +end + +class Fax < Tax + def store_id; 1 end +end + # TODO: test nested models class Response::Nested < Response; end @@ -27,6 +41,8 @@ class PolymorphicRoutesTest < ActiveSupport::TestCase def setup @article = Article.new @response = Response.new + @tax = Tax.new + @fax = Fax.new end def test_with_record @@ -205,4 +221,73 @@ class PolymorphicRoutesTest < ActiveSupport::TestCase polymorphic_url(path) end end + + # Tests for names where .plural.singular doesn't round-trip + def test_with_irregular_plural_record + @tax.save + expects(:taxis_url).with(@tax) + polymorphic_url(@tax) + end + + def test_with_irregular_plural_new_record + expects(:taxes_url).with() + @tax.expects(:new_record?).returns(true) + polymorphic_url(@tax) + end + + def test_with_irregular_plural_record_and_action + expects(:new_taxis_url).with() + @tax.expects(:new_record?).never + polymorphic_url(@tax, :action => 'new') + end + + def test_irregular_plural_url_helper_prefixed_with_new + expects(:new_taxis_url).with() + new_polymorphic_url(@tax) + end + + def test_irregular_plural_url_helper_prefixed_with_edit + @tax.save + expects(:edit_taxis_url).with(@tax) + edit_polymorphic_url(@tax) + end + + def test_with_nested_irregular_plurals + @fax.save + expects(:taxis_faxis_url).with(@tax, @fax) + polymorphic_url([@tax, @fax]) + end + + def test_with_nested_unsaved_irregular_plurals + expects(:taxis_faxes_url).with(@tax) + polymorphic_url([@tax, @fax]) + end + + def test_new_with_irregular_plural_array_and_namespace + expects(:new_admin_taxis_url).with() + polymorphic_url([:admin, @tax], :action => 'new') + end + + def test_unsaved_with_irregular_plural_array_and_namespace + expects(:admin_taxes_url).with() + polymorphic_url([:admin, @tax]) + end + + def test_nesting_with_irregular_plurals_and_array_ending_in_singleton_resource + expects(:taxis_faxis_url).with(@tax) + polymorphic_url([@tax, :faxis]) + end + + def test_with_array_containing_single_irregular_plural_object + @tax.save + expects(:taxis_url).with(@tax) + polymorphic_url([nil, @tax]) + end + + def test_with_array_containing_single_name_irregular_plural + @tax.save + expects(:taxes_url) + polymorphic_url([:taxes]) + end + end diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb index b550d3db78..89bf4fdacc 100644 --- a/actionpack/test/controller/rack_test.rb +++ b/actionpack/test/controller/rack_test.rb @@ -258,7 +258,7 @@ class RackResponseTest < BaseRackTest }, headers) parts = [] - body.each { |part| parts << part } + body.each { |part| parts << part.to_s } assert_equal ["0", "1", "2", "3", "4"], parts end end diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index 27cedc91d2..91e21db854 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -212,7 +212,7 @@ class RedirectTest < ActionController::TestCase end def test_redirect_to_back_with_no_referer - assert_raises(ActionController::RedirectBackError) { + assert_raise(ActionController::RedirectBackError) { @request.env["HTTP_REFERER"] = nil get :redirect_to_back } @@ -239,7 +239,7 @@ class RedirectTest < ActionController::TestCase end def test_redirect_to_nil - assert_raises(ActionController::ActionControllerError) do + assert_raise(ActionController::ActionControllerError) do get :redirect_to_nil end end diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index e131a735a9..a52931565d 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -36,6 +36,39 @@ class TestController < ActionController::Base render :action => 'hello_world' end end + + def conditional_hello_with_public_header + if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) + render :action => 'hello_world' + end + end + + def conditional_hello_with_public_header_and_expires_at + expires_in 1.minute + if stale?(:last_modified => Time.now.utc.beginning_of_day, :etag => [:foo, 123], :public => true) + render :action => 'hello_world' + end + end + + def conditional_hello_with_expires_in + expires_in 1.minute + render :action => 'hello_world' + end + + def conditional_hello_with_expires_in_with_public + expires_in 1.minute, :public => true + render :action => 'hello_world' + end + + def conditional_hello_with_expires_in_with_public_with_more_keys + expires_in 1.minute, :public => true, 'max-stale' => 5.hours + render :action => 'hello_world' + end + + def conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax + expires_in 1.minute, :public => true, :private => nil, 'max-stale' => 5.hours + render :action => 'hello_world' + end def conditional_hello_with_bangs render :action => 'hello_world' @@ -124,6 +157,11 @@ class TestController < ActionController::Base render :file => 'test/dot.directory/render_file_with_ivar' end + def render_file_using_pathname + @secret = 'in the sauce' + render :file => Pathname.new(File.dirname(__FILE__)).join('..', 'fixtures', 'test', 'dot.directory', 'render_file_with_ivar.erb') + end + def render_file_from_template @secret = 'in the sauce' @path = File.expand_path(File.join(File.dirname(__FILE__), '../fixtures/test/render_file_with_ivar.erb')) @@ -280,6 +318,10 @@ class TestController < ActionController::Base def render_implicit_js_template_without_layout end + def render_html_explicit_template_and_layout + render :template => 'test/render_implicit_html_template_from_xhr_request', :layout => 'layouts/default_html' + end + def formatted_html_erb end @@ -645,6 +687,14 @@ class TestController < ActionController::Base render :partial => "hash_object", :object => {:first_name => "Sam"} end + def partial_with_nested_object + render :partial => "quiz/questions/question", :object => Quiz::Question.new("first") + end + + def partial_with_nested_object_shorthand + render Quiz::Question.new("first") + end + def partial_hash_collection render :partial => "hash_object", :collection => [ {:first_name => "Pratik"}, {:first_name => "Amy"} ] end @@ -684,12 +734,15 @@ class TestController < ActionController::Base "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", - "render_implicit_js_template_without_layout" + "delete_with_js", "update_page", "update_page_with_instance_variables" "layouts/standard" + when "render_implicit_js_template_without_layout" + "layouts/default_html" when "action_talk_to_layout", "layout_overriding_layout" "layouts/talk_from_action" + when "render_implicit_html_template_from_xhr_request" + (request.xhr? ? 'layouts/xhr' : 'layouts/standard') end end end @@ -783,6 +836,11 @@ class RenderTest < ActionController::TestCase assert_equal "<html>hello world, I'm here!</html>", @response.body end + def test_xhr_with_render_text_and_layout + xhr :get, :render_text_hello_world_with_layout + assert_equal "<html>hello world, I'm here!</html>", @response.body + end + def test_do_with_render_action_and_layout_false get :hello_world_with_layout_false assert_equal 'Hello world!', @response.body @@ -808,6 +866,11 @@ class RenderTest < ActionController::TestCase assert_equal "The secret is in the sauce\n", @response.body end + def test_render_file_using_pathname + get :render_file_using_pathname + assert_equal "The secret is in the sauce\n", @response.body + end + def test_render_file_with_locals get :render_file_with_locals assert_equal "The secret is in the sauce\n", @response.body @@ -884,11 +947,11 @@ class RenderTest < ActionController::TestCase end def test_attempt_to_access_object_method - assert_raises(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } + assert_raise(ActionController::UnknownAction, "No action responded to [clone]") { get :clone } end def test_private_methods - assert_raises(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } + assert_raise(ActionController::UnknownAction, "No action responded to [determine_layout]") { get :determine_layout } end def test_access_to_request_in_view @@ -1018,8 +1081,13 @@ class RenderTest < ActionController::TestCase end def test_should_implicitly_render_html_template_from_xhr_request - get :render_implicit_html_template_from_xhr_request, :format => :js - assert_equal "Hello HTML!", @response.body + xhr :get, :render_implicit_html_template_from_xhr_request + assert_equal "XHR!\nHello HTML!", @response.body + end + + def test_should_render_explicit_html_template_with_html_layout + xhr :get, :render_html_explicit_template_and_layout + assert_equal "<html>Hello HTML!</html>\n", @response.body end def test_should_implicitly_render_js_template_without_layout @@ -1115,7 +1183,7 @@ class RenderTest < ActionController::TestCase end def test_bad_render_to_string_still_throws_exception - assert_raises(ActionView::MissingTemplate) { get :render_to_string_with_exception } + assert_raise(ActionView::MissingTemplate) { get :render_to_string_with_exception } end def test_render_to_string_that_throws_caught_exception_doesnt_break_assigns @@ -1140,15 +1208,15 @@ class RenderTest < ActionController::TestCase end def test_double_render - assert_raises(ActionController::DoubleRenderError) { get :double_render } + assert_raise(ActionController::DoubleRenderError) { get :double_render } end def test_double_redirect - assert_raises(ActionController::DoubleRenderError) { get :double_redirect } + assert_raise(ActionController::DoubleRenderError) { get :double_redirect } end def test_render_and_redirect - assert_raises(ActionController::DoubleRenderError) { get :render_and_redirect } + assert_raise(ActionController::DoubleRenderError) { get :render_and_redirect } end # specify the one exception to double render rule - render_to_string followed by render @@ -1429,6 +1497,16 @@ class RenderTest < ActionController::TestCase assert_equal "Sam\nmaS\n", @response.body end + def test_partial_with_nested_object + get :partial_with_nested_object + assert_equal "first", @response.body + end + + def test_partial_with_nested_object_shorthand + get :partial_with_nested_object_shorthand + assert_equal "first", @response.body + end + def test_hash_partial_collection get :partial_hash_collection assert_equal "Pratik\nkitarP\nAmy\nymA\n", @response.body @@ -1447,7 +1525,7 @@ class RenderTest < ActionController::TestCase end def test_render_missing_partial_template - assert_raises(ActionView::MissingTemplate) do + assert_raise(ActionView::MissingTemplate) do get :missing_partial end end @@ -1463,6 +1541,35 @@ class RenderTest < ActionController::TestCase end end +class ExpiresInRenderTest < ActionController::TestCase + tests TestController + + def setup + @request.host = "www.nextangle.com" + end + + def test_expires_in_header + get :conditional_hello_with_expires_in + assert_equal "max-age=60, private", @response.headers["Cache-Control"] + end + + def test_expires_in_header_with_public + get :conditional_hello_with_expires_in_with_public + assert_equal "max-age=60, public", @response.headers["Cache-Control"] + end + + def test_expires_in_header_with_additional_headers + get :conditional_hello_with_expires_in_with_public_with_more_keys + assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] + end + + def test_expires_in_old_syntax + get :conditional_hello_with_expires_in_with_public_with_more_keys_old_syntax + assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] + end +end + + class EtagRenderTest < ActionController::TestCase tests TestController @@ -1552,6 +1659,16 @@ class EtagRenderTest < ActionController::TestCase get :conditional_hello_with_bangs assert_response :not_modified end + + def test_etag_with_public_true_should_set_header + get :conditional_hello_with_public_header + assert_equal "public", @response.headers['Cache-Control'] + end + + def test_etag_with_public_true_should_set_header_and_retain_other_headers + get :conditional_hello_with_public_header_and_expires_at + assert_equal "max-age=60, public", @response.headers['Cache-Control'] + end protected def etag_for(text) diff --git a/actionpack/test/controller/request/multipart_params_parsing_test.rb b/actionpack/test/controller/request/multipart_params_parsing_test.rb index 054519d0d2..b812072ef4 100644 --- a/actionpack/test/controller/request/multipart_params_parsing_test.rb +++ b/actionpack/test/controller/request/multipart_params_parsing_test.rb @@ -103,7 +103,7 @@ class MultipartParamsParsingTest < ActionController::IntegrationTest test "does not create tempfile if no file has been selected" do params = parse_multipart('none') - assert_equal %w(files submit-name), params.keys.sort + assert_equal %w(submit-name), params.keys.sort assert_equal 'Larry', params['submit-name'] assert_equal nil, params['files'] end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index ef0bf5fd08..835e73e3ab 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -79,17 +79,17 @@ module RequestForgeryProtectionTests def test_should_not_allow_html_post_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { post :index, :format => :html } end def test_should_not_allow_html_put_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { put :index, :format => :html } end def test_should_not_allow_html_delete_without_token @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s - assert_raises(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } + assert_raise(ActionController::InvalidAuthenticityToken) { delete :index, :format => :html } end def test_should_allow_api_formatted_post_without_token @@ -111,42 +111,42 @@ module RequestForgeryProtectionTests end def test_should_not_allow_api_formatted_post_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s put :index, :format => 'xml' end end def test_should_not_allow_api_formatted_delete_sent_as_url_encoded_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::URL_ENCODED_FORM.to_s delete :index, :format => 'xml' end end def test_should_not_allow_api_formatted_post_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s post :index, :format => 'xml' end end def test_should_not_allow_api_formatted_put_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s put :index, :format => 'xml' end end def test_should_not_allow_api_formatted_delete_sent_as_multipart_form_without_token - assert_raises(ActionController::InvalidAuthenticityToken) do + assert_raise(ActionController::InvalidAuthenticityToken) do @request.env['CONTENT_TYPE'] = Mime::MULTIPART_FORM.to_s delete :index, :format => 'xml' end diff --git a/actionpack/test/controller/request_test.rb b/actionpack/test/controller/request_test.rb index efe4f136f5..c72f885a05 100644 --- a/actionpack/test/controller/request_test.rb +++ b/actionpack/test/controller/request_test.rb @@ -59,7 +59,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal '3.4.5.6', @request.remote_ip @request.env['HTTP_CLIENT_IP'] = '8.8.8.8' - e = assert_raises(ActionController::ActionControllerError) { + e = assert_raise(ActionController::ActionControllerError) { @request.remote_ip } assert_match /IP spoofing attack/, e.message @@ -297,7 +297,7 @@ class RequestTest < ActiveSupport::TestCase end def test_invalid_http_method_raises_exception - assert_raises(ActionController::UnknownHttpMethod) do + assert_raise(ActionController::UnknownHttpMethod) do self.request_method = :random_method @request.request_method end @@ -311,7 +311,7 @@ class RequestTest < ActiveSupport::TestCase end def test_invalid_method_hacking_on_post_raises_exception - assert_raises(ActionController::UnknownHttpMethod) do + assert_raise(ActionController::UnknownHttpMethod) do self.request_method = :_random_method @request.request_method end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index ae2639d245..c807e71cd7 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -99,7 +99,7 @@ class ResourcesTest < ActionController::TestCase expected_options = {:controller => 'messages', :action => 'show', :id => '1.1.1'} with_restful_routing :messages do - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do assert_recognizes(expected_options, :path => 'messages/1.1.1', :method => :get) end end @@ -175,6 +175,24 @@ class ResourcesTest < ActionController::TestCase end end + def test_with_collection_actions_and_name_prefix_and_member_action_with_same_name + actions = { 'a' => :get } + + with_restful_routing :messages, :path_prefix => '/threads/:thread_id', :name_prefix => "thread_", :collection => actions, :member => actions do + assert_restful_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.each do |action, method| + assert_recognizes(options.merge(:action => action), :path => "/threads/1/messages/#{action}", :method => method) + end + end + + assert_restful_named_routes_for :messages, :path_prefix => 'threads/1/', :name_prefix => 'thread_', :options => { :thread_id => '1' } do |options| + actions.keys.each do |action| + assert_named_route "/threads/1/messages/#{action}", "#{action}_thread_messages_path", :action => action + end + end + end + end + def test_with_collection_action_and_name_prefix_and_formatted actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } @@ -209,6 +227,14 @@ class ResourcesTest < ActionController::TestCase end end + def test_with_member_action_and_requirement + expected_options = {:controller => 'messages', :action => 'mark', :id => '1.1.1'} + + with_restful_routing(:messages, :requirements => {:id => /[0-9]\.[0-9]\.[0-9]/}, :member => { :mark => :get }) do + assert_recognizes(expected_options, :path => 'messages/1.1.1/mark', :method => :get) + end + end + def test_member_when_override_paths_for_default_restful_actions_with [:put, :post].each do |method| with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do @@ -325,7 +351,7 @@ class ResourcesTest < ActionController::TestCase with_restful_routing :messages do assert_restful_routes_for :messages do |options| assert_recognizes(options.merge(:action => "new"), :path => "/messages/new", :method => :get) - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do ActionController::Routing::Routes.recognize_path("/messages/new", :method => :post) end end @@ -406,6 +432,34 @@ class ResourcesTest < ActionController::TestCase end end + def test_shallow_nested_restful_routes_with_namespaces + with_routing do |set| + set.draw do |map| + map.namespace :backoffice do |map| + map.namespace :admin do |map| + map.resources :products, :shallow => true do |map| + map.resources :images + end + end + end + end + + assert_simply_restful_for :products, + :controller => 'backoffice/admin/products', + :namespace => 'backoffice/admin/', + :name_prefix => 'backoffice_admin_', + :path_prefix => 'backoffice/admin/', + :shallow => true + assert_simply_restful_for :images, + :controller => 'backoffice/admin/images', + :namespace => 'backoffice/admin/', + :name_prefix => 'backoffice_admin_product_', + :path_prefix => 'backoffice/admin/products/1/', + :shallow => true, + :options => { :product_id => '1' } + end + end + def test_restful_routes_dont_generate_duplicates with_restful_routing :messages do routes = ActionController::Routing::Routes.routes @@ -583,11 +637,11 @@ class ResourcesTest < ActionController::TestCase options = { :controller => controller_name.to_s } collection_path = "/#{controller_name}" - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) end - assert_raises(ActionController::MethodNotAllowed) do + assert_raise(ActionController::MethodNotAllowed) do assert_recognizes(options.merge(:action => 'destroy'), :path => collection_path, :method => :delete) end end @@ -596,7 +650,7 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_head_method_for_member_routes with_routing do |set| set.draw do |map| - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do map.resources :messages, :member => {:something => :head} end end @@ -606,7 +660,7 @@ class ResourcesTest < ActionController::TestCase def test_should_not_allow_invalid_http_methods_for_member_routes with_routing do |set| set.draw do |map| - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do map.resources :messages, :member => {:something => :invalid} end end @@ -750,9 +804,17 @@ class ResourcesTest < ActionController::TestCase end def test_with_path_segment - with_restful_routing :messages, :as => 'reviews' do - assert_simply_restful_for :messages, :as => 'reviews' + with_restful_routing :messages do + assert_simply_restful_for :messages + assert_recognizes({:controller => "messages", :action => "index"}, "/messages") + assert_recognizes({:controller => "messages", :action => "index"}, "/messages/") end + + with_restful_routing :messages, :as => 'reviews' do + assert_simply_restful_for :messages, :as => 'reviews' + assert_recognizes({:controller => "messages", :action => "index"}, "/reviews") + assert_recognizes({:controller => "messages", :action => "index"}, "/reviews/") + end end def test_multiple_with_path_segment_and_controller @@ -1066,7 +1128,7 @@ class ResourcesTest < ActionController::TestCase path = "#{options[:as] || controller_name}" collection_path = "/#{options[:path_prefix]}#{path}" - shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" member_path = "#{shallow_path}/1" new_path = "#{collection_path}/#{new_action}" edit_member_path = "#{member_path}/#{edit_action}" @@ -1130,10 +1192,10 @@ class ResourcesTest < ActionController::TestCase options[:options].delete :action path = "#{options[:as] || controller_name}" - shallow_path = "/#{options[:path_prefix] unless options[:shallow]}#{path}" + shallow_path = "/#{options[:shallow] ? options[:namespace] : options[:path_prefix]}#{path}" full_path = "/#{options[:path_prefix]}#{path}" name_prefix = options[:name_prefix] - shallow_prefix = "#{options[:name_prefix] unless options[:shallow]}" + shallow_prefix = options[:shallow] ? options[:namespace].try(:gsub, /\//, '_') : options[:name_prefix] new_action = "new" edit_action = "edit" diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 13ba0c30dd..ef56119751 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -219,7 +219,7 @@ class DynamicSegmentTest < Test::Unit::TestCase a_value = nil # Local jump because of return inside eval. - assert_raises(LocalJumpError) { eval(segment.extraction_code) } + assert_raise(LocalJumpError) { eval(segment.extraction_code) } end def test_extraction_code_should_return_on_mismatch @@ -229,7 +229,7 @@ class DynamicSegmentTest < Test::Unit::TestCase a_value = nil # Local jump because of return inside eval. - assert_raises(LocalJumpError) { eval(segment.extraction_code) } + assert_raise(LocalJumpError) { eval(segment.extraction_code) } end def test_extraction_code_should_accept_value_and_set_local @@ -494,7 +494,7 @@ class RouteBuilderTest < Test::Unit::TestCase defaults = {:action => 'buy', :person => nil, :car => nil} requirements = {:person => /\w+/, :car => /^\w+$/} - assert_raises ArgumentError do + assert_raise ArgumentError do route_requirements = builder.assign_route_options(segments, defaults, requirements) end @@ -882,7 +882,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase end assert_equal({:controller => "admin/accounts", :action => "index"}, rs.recognize_path("/admin/accounts")) assert_equal({:controller => "admin/users", :action => "index"}, rs.recognize_path("/admin/users")) - assert_raises(ActionController::RoutingError) { rs.recognize_path("/admin/products") } + assert_raise(ActionController::RoutingError) { rs.recognize_path("/admin/products") } end def test_route_with_regexp_and_dot @@ -955,6 +955,13 @@ class LegacyRouteSetTests < Test::Unit::TestCase x.send(:page_url)) end + def test_named_route_with_blank_path_prefix + rs.add_named_route :page, 'page', :controller => 'content', :action => 'show_page', :path_prefix => '' + x = setup_for_named_route + assert_equal("http://test.host/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 @@ -1060,11 +1067,11 @@ class LegacyRouteSetTests < Test::Unit::TestCase rs.draw do |map| map.connect ':controller/:action/:id' end - assert_raises(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } + assert_raise(ActionController::RoutingError) { rs.recognize_path("/not_a/show/10") } end def test_paths_do_not_accept_defaults - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do rs.draw do |map| map.path 'file/*path', :controller => 'content', :action => 'show_file', :path => %w(fake default) map.connect ':controller/:action/:id' @@ -1197,7 +1204,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase assert_equal '/post/10', rs.generate(:controller => 'post', :action => 'show', :id => 10) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do rs.generate(:controller => 'post', :action => 'show') end end @@ -1407,7 +1414,7 @@ class LegacyRouteSetTests < Test::Unit::TestCase end x = setup_for_named_route - assert_raises(ActionController::RoutingError) do + assert_raise(ActionController::RoutingError) do x.send(:foo_with_requirement_url, "I am Against the requirements") end end @@ -1539,7 +1546,7 @@ class RouteTest < Test::Unit::TestCase end def test_builder_complains_without_controller - assert_raises(ArgumentError) do + assert_raise(ArgumentError) do ROUTING::RouteBuilder.new.build '/contact', :contoller => "contact", :action => "index" end end @@ -1822,27 +1829,27 @@ class RouteSetTest < Test::Unit::TestCase end def test_route_requirements_with_anchor_chars_are_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /^\d+/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\A\d+/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+$/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\Z/ end end - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+\z/ end @@ -1851,22 +1858,30 @@ class RouteSetTest < Test::Unit::TestCase set.draw do |map| map.connect 'page/:id', :controller => 'pages', :action => 'show', :id => /\d+/, :name => /^(david|jamis)/ end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate :controller => 'pages', :action => 'show', :id => 10 end end end def test_route_requirements_with_invalid_http_method_is_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :invalid} end end end + def test_route_requirements_with_options_method_condition_is_valid + assert_nothing_raised do + set.draw do |map| + map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :options} + end + end + end + def test_route_requirements_with_head_method_condition_is_invalid - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'valid/route', :controller => 'pages', :action => 'show', :conditions => {:method => :head} end @@ -1878,10 +1893,10 @@ class RouteSetTest < Test::Unit::TestCase map.connect 'page/37s', :controller => 'pages', :action => 'show', :name => /(jamis|david)/ end assert_equal '/page/37s', set.generate(:controller => 'pages', :action => 'show', :name => 'jamis') - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate(:controller => 'pages', :action => 'show', :name => 'not_jamis') end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.generate(:controller => 'pages', :action => 'show', :name => 'nor_jamis_and_david') end end @@ -1924,7 +1939,7 @@ class RouteSetTest < Test::Unit::TestCase assert_equal("update", request.path_parameters[:action]) request.recycle! - assert_raises(ActionController::UnknownHttpMethod) { + assert_raise(ActionController::UnknownHttpMethod) { request.env["REQUEST_METHOD"] = "BACON" set.recognize(request) } @@ -2122,11 +2137,9 @@ class RouteSetTest < Test::Unit::TestCase Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) set.draw do |map| - map.namespace 'api', :path_prefix => 'prefix' do |api| api.route 'inventory', :controller => "products", :action => 'inventory' end - end request.path = "/prefix/inventory" @@ -2138,6 +2151,24 @@ class RouteSetTest < Test::Unit::TestCase Object.send(:remove_const, :Api) end + def test_namespace_with_blank_path_prefix + Object.const_set(:Api, Module.new { |m| m.const_set(:ProductsController, Class.new) }) + + set.draw do |map| + map.namespace 'api', :path_prefix => '' do |api| + api.route 'inventory', :controller => "products", :action => 'inventory' + end + end + + request.path = "/inventory" + 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]) + ensure + Object.send(:remove_const, :Api) + end + def test_generate_finds_best_fit set.draw do |map| map.connect "/people", :controller => "people", :action => "index" @@ -2202,6 +2233,13 @@ class RouteSetTest < Test::Unit::TestCase assert_equal "/my/foo/bar/7?x=y", set.generate(args) end + def test_generate_with_blank_path_prefix + set.draw { |map| map.connect ':controller/:action/:id', :path_prefix => '' } + + args = { :controller => "foo", :action => "bar", :id => "7", :x => "y" } + assert_equal "/foo/bar/7?x=y", set.generate(args) + end + def test_named_routes_are_never_relative_to_modules set.draw do |map| map.connect "/connection/manage/:action", :controller => 'connection/manage' @@ -2237,6 +2275,22 @@ class RouteSetTest < Test::Unit::TestCase ) end + def test_format_is_not_inherit + set.draw do |map| + map.connect '/posts.:format', :controller => 'posts' + end + + assert_equal '/posts', set.generate( + {:controller => 'posts'}, + {:controller => 'posts', :action => 'index', :format => 'xml'} + ) + + assert_equal '/posts.xml', set.generate( + {:controller => 'posts', :format => 'xml'}, + {:controller => 'posts', :action => 'index', :format => 'xml'} + ) + end + def test_expiry_determination_should_consider_values_with_to_param set.draw { |map| map.connect 'projects/:project_id/:controller/:action' } assert_equal '/projects/1/post/show', set.generate( @@ -2293,7 +2347,7 @@ class RouteSetTest < Test::Unit::TestCase end def test_route_requirements_with_unsupported_regexp_options_must_error - assert_raises ArgumentError do + assert_raise ArgumentError do set.draw do |map| map.connect 'page/:name', :controller => 'pages', :action => 'show', @@ -2331,7 +2385,7 @@ class RouteSetTest < Test::Unit::TestCase :requirements => {:name => /(david|jamis)/i} end assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/davidjamis') end assert_equal({:controller => 'pages', :action => 'show', :name => 'DAVID'}, set.recognize_path('/page/DAVID')) @@ -2345,7 +2399,7 @@ class RouteSetTest < Test::Unit::TestCase end url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) assert_equal "/page/david", url - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) end url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) @@ -2365,10 +2419,10 @@ class RouteSetTest < Test::Unit::TestCase end assert_equal({:controller => 'pages', :action => 'show', :name => 'jamis'}, set.recognize_path('/page/jamis')) assert_equal({:controller => 'pages', :action => 'show', :name => 'david'}, set.recognize_path('/page/david')) - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/david #The Creator') end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do set.recognize_path('/page/David') end end @@ -2386,10 +2440,10 @@ class RouteSetTest < Test::Unit::TestCase end url = set.generate({:controller => 'pages', :action => 'show', :name => 'david'}) assert_equal "/page/david", url - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'davidjamis'}) end - assert_raises ActionController::RoutingError do + assert_raise ActionController::RoutingError do url = set.generate({:controller => 'pages', :action => 'show', :name => 'JAMIS'}) end end diff --git a/actionpack/test/controller/selector_test.rb b/actionpack/test/controller/selector_test.rb index 4e31b4573c..9d0613d1e2 100644 --- a/actionpack/test/controller/selector_test.rb +++ b/actionpack/test/controller/selector_test.rb @@ -303,7 +303,7 @@ class SelectorTest < Test::Unit::TestCase assert_equal 1, @matches.size assert_equal "2", @matches[0].attributes["id"] # Before first and past last returns nothing.: - assert_raises(ArgumentError) { select("tr:nth-child(-1)") } + assert_raise(ArgumentError) { select("tr:nth-child(-1)") } select("tr:nth-child(0)") assert_equal 0, @matches.size select("tr:nth-child(5)") @@ -597,8 +597,8 @@ class SelectorTest < Test::Unit::TestCase def test_negation_details parse(%Q{<p id="1"></p><p id="2"></p><p id="3"></p>}) - assert_raises(ArgumentError) { select(":not(") } - assert_raises(ArgumentError) { select(":not(:not())") } + assert_raise(ArgumentError) { select(":not(") } + assert_raise(ArgumentError) { select(":not(:not())") } select("p:not(#1):not(#3)") assert_equal 1, @matches.size assert_equal "2", @matches[0].attributes["id"] diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 5fc79baa44..3d1904fee9 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -44,12 +44,12 @@ class SendFileTest < ActionController::TestCase response = nil assert_nothing_raised { response = process('file') } assert_not_nil response - assert_kind_of Proc, response.body + assert_kind_of Proc, response.body_parts require 'stringio' output = StringIO.new output.binmode - assert_nothing_raised { response.body.call(response, output) } + assert_nothing_raised { response.body_parts.call(response, output) } assert_equal file_data, output.string end @@ -142,7 +142,7 @@ class SendFileTest < ActionController::TestCase } @controller.headers = {} - assert_raises(ArgumentError){ @controller.send(:send_file_headers!, options) } + assert_raise(ArgumentError){ @controller.send(:send_file_headers!, options) } end %w(file data).each do |method| diff --git a/actionpack/test/controller/session/cookie_store_test.rb b/actionpack/test/controller/session/cookie_store_test.rb index 9c93ca6539..48a961ca34 100644 --- a/actionpack/test/controller/session/cookie_store_test.rb +++ b/actionpack/test/controller/session/cookie_store_test.rb @@ -199,29 +199,18 @@ class CookieStoreTest < ActionController::IntegrationTest with_test_route_set do # First request accesses the session - time = Time.local(2008, 4, 24) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - cookies[SessionKey] = SignedBar get '/set_session_value' assert_response :success + cookie = headers['Set-Cookie'] - cookie_body = response.body - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] - - # Second request does not access the session - time = Time.local(2008, 4, 25) - Time.stubs(:now).returns(time) - expected_expiry = (time + 5.hours).gmtime.strftime("%a, %d-%b-%Y %H:%M:%S GMT") - + # Second request does not access the session so the + # expires header should not be changed get '/no_session_access' assert_response :success - - assert_equal "_myapp_session=#{cookie_body}; path=/; expires=#{expected_expiry}; HttpOnly", - headers['Set-Cookie'] + assert_equal cookie, headers['Set-Cookie'], + "#{unmarshal_session(cookie).inspect} expected but was #{unmarshal_session(headers['Set-Cookie']).inspect}" end end @@ -236,4 +225,13 @@ class CookieStoreTest < ActionController::IntegrationTest yield end end + + def unmarshal_session(cookie_string) + session = Rack::Utils.parse_query(cookie_string, ';,').inject({}) {|h,(k,v)| + h[k] = Array === v ? v.first : v + h + }[SessionKey] + verifier = ActiveSupport::MessageVerifier.new(SessionSecret, 'SHA1') + verifier.verify(session) + end end diff --git a/actionpack/test/controller/session/mem_cache_store_test.rb b/actionpack/test/controller/session/mem_cache_store_test.rb index c3a6c8ce45..2f80a3c7c2 100644 --- a/actionpack/test/controller/session/mem_cache_store_test.rb +++ b/actionpack/test/controller/session/mem_cache_store_test.rb @@ -17,11 +17,14 @@ class MemCacheStoreTest < ActionController::IntegrationTest end def get_session_id - render :text => "foo: #{session[:foo].inspect}; id: #{request.session_options[:id]}" + session[:foo] + render :text => "#{request.session_options[:id]}" end def call_reset_session + session[:bar] reset_session + session[:bar] = "baz" head :ok end @@ -58,47 +61,52 @@ class MemCacheStoreTest < ActionController::IntegrationTest end end - def test_getting_session_id + def test_setting_session_value_after_session_reset with_test_route_set do get '/set_session_value' assert_response :success assert cookies['_session_id'] session_id = cookies['_session_id'] - get '/get_session_id' + get '/call_reset_session' assert_response :success - assert_equal "foo: \"bar\"; id: #{session_id}", response.body - end - end + assert_not_equal [], headers['Set-Cookie'] - def test_prevents_session_fixation - with_test_route_set do get '/get_session_value' assert_response :success assert_equal 'foo: nil', response.body - session_id = cookies['_session_id'] - - reset! - get '/set_session_value', :_session_id => session_id + get '/get_session_id' assert_response :success - assert_equal nil, cookies['_session_id'] + assert_not_equal session_id, response.body end end - def test_setting_session_value_after_session_reset + def test_getting_session_id with_test_route_set do get '/set_session_value' assert_response :success assert cookies['_session_id'] + session_id = cookies['_session_id'] - get '/call_reset_session' + get '/get_session_id' assert_response :success - assert_not_equal [], headers['Set-Cookie'] + assert_equal session_id, response.body + end + end + def test_prevents_session_fixation + with_test_route_set do get '/get_session_value' assert_response :success assert_equal 'foo: nil', response.body + session_id = cookies['_session_id'] + + reset! + + get '/set_session_value', :_session_id => session_id + assert_response :success + assert_equal nil, cookies['_session_id'] end end rescue LoadError, RuntimeError diff --git a/actionpack/test/controller/url_rewriter_test.rb b/actionpack/test/controller/url_rewriter_test.rb index 09a8356fec..863f8414c5 100644 --- a/actionpack/test/controller/url_rewriter_test.rb +++ b/actionpack/test/controller/url_rewriter_test.rb @@ -99,7 +99,7 @@ class UrlWriterTests < ActionController::TestCase end def test_exception_is_thrown_without_host - assert_raises RuntimeError do + assert_raise RuntimeError do W.new.url_for :controller => 'c', :action => 'a', :id => 'i' end end diff --git a/actionpack/test/fixtures/layouts/default_html.html.erb b/actionpack/test/fixtures/layouts/default_html.html.erb new file mode 100644 index 0000000000..edd719111c --- /dev/null +++ b/actionpack/test/fixtures/layouts/default_html.html.erb @@ -0,0 +1 @@ +<html><%= @content_for_layout %></html> diff --git a/actionpack/test/fixtures/layouts/xhr.html.erb b/actionpack/test/fixtures/layouts/xhr.html.erb new file mode 100644 index 0000000000..85285324ec --- /dev/null +++ b/actionpack/test/fixtures/layouts/xhr.html.erb @@ -0,0 +1,2 @@ +XHR! +<%= yield %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/quiz/questions/_question.html.erb b/actionpack/test/fixtures/quiz/questions/_question.html.erb new file mode 100644 index 0000000000..fb4dcfee64 --- /dev/null +++ b/actionpack/test/fixtures/quiz/questions/_question.html.erb @@ -0,0 +1 @@ +<%= question.name %>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ b/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ new file mode 100644 index 0000000000..d009950384 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.en.html.erb~ @@ -0,0 +1 @@ +Don't render me!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.erb~ b/actionpack/test/fixtures/test/malformed/malformed.erb~ new file mode 100644 index 0000000000..d009950384 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.erb~ @@ -0,0 +1 @@ +Don't render me!
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/malformed/malformed.html.erb~ b/actionpack/test/fixtures/test/malformed/malformed.html.erb~ new file mode 100644 index 0000000000..d009950384 --- /dev/null +++ b/actionpack/test/fixtures/test/malformed/malformed.html.erb~ @@ -0,0 +1 @@ +Don't render me!
\ No newline at end of file diff --git a/actionpack/test/template/active_record_helper_test.rb b/actionpack/test/template/active_record_helper_test.rb index e46f95d18b..83c028b5f2 100644 --- a/actionpack/test/template/active_record_helper_test.rb +++ b/actionpack/test/template/active_record_helper_test.rb @@ -19,6 +19,30 @@ class ActiveRecordHelperTest < ActionView::TestCase Column = Struct.new("Column", :type, :name, :human_name) end + class DirtyPost + class Errors + def empty? + false + end + + def count + 1 + end + + def full_messages + ["Author name can't be <em>empty</em>"] + end + + def on(field) + "can't be <em>empty</em>" + end + end + + def errors + Errors.new + end + end + def setup_post @post = Post.new def @post.errors @@ -195,10 +219,20 @@ class ActiveRecordHelperTest < ActionView::TestCase assert_equal %(<div class="errorDeathByClass"><h1>1 error prohibited this post from being saved</h1><p>There were problems with the following fields:</p><ul><li>Author name can't be empty</li></ul></div>), error_messages_for("post", :class => "errorDeathByClass", :id => nil, :header_tag => "h1") end + def test_error_messages_for_escapes_html + @dirty_post = DirtyPost.new + assert_dom_equal %(<div class="errorExplanation" id="errorExplanation"><h2>1 error prohibited this dirty post from being saved</h2><p>There were problems with the following fields:</p><ul><li>Author name can't be <em>empty</em></li></ul></div>), error_messages_for("dirty_post") + end + def test_error_messages_for_handles_nil assert_equal "", error_messages_for("notthere") end + def test_error_message_on_escapes_html + @dirty_post = DirtyPost.new + assert_dom_equal "<div class=\"formError\">can't be <em>empty</em></div>", error_message_on(:dirty_post, :author_name) + end + def test_error_message_on_handles_nil assert_equal "", error_message_on("notthere", "notthere") end diff --git a/actionpack/test/template/body_parts_test.rb b/actionpack/test/template/body_parts_test.rb new file mode 100644 index 0000000000..4c82b75cdc --- /dev/null +++ b/actionpack/test/template/body_parts_test.rb @@ -0,0 +1,22 @@ +require 'abstract_unit' + +class BodyPartsTest < ActionController::TestCase + RENDERINGS = [Object.new, Object.new, Object.new] + + class TestController < ActionController::Base + def index + RENDERINGS.each do |rendering| + response.template.punctuate_body! rendering + end + @performed_render = true + end + end + + tests TestController + + def test_body_parts + get :index + assert_equal RENDERINGS, @response.body_parts + assert_equal RENDERINGS.join, @response.body + end +end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 5cc81b4afb..654eee40a3 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1001,6 +1001,47 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_labelled_builder_with_nested_fields_for_without_options_hash + klass = nil + + form_for(:post, @post, :builder => LabelledFormBuilder) do |f| + f.fields_for(:comments, Comment.new) do |nested_fields| + klass = nested_fields.class + '' + end + end + + assert_equal LabelledFormBuilder, klass + end + + def test_form_for_with_labelled_builder_with_nested_fields_for_with_options_hash + klass = nil + + form_for(:post, @post, :builder => LabelledFormBuilder) do |f| + f.fields_for(:comments, Comment.new, :index => 'foo') do |nested_fields| + klass = nested_fields.class + '' + end + end + + assert_equal LabelledFormBuilder, klass + end + + class LabelledFormBuilderSubclass < LabelledFormBuilder; end + + def test_form_for_with_labelled_builder_with_nested_fields_for_with_custom_builder + klass = nil + + form_for(:post, @post, :builder => LabelledFormBuilder) do |f| + f.fields_for(:comments, Comment.new, :builder => LabelledFormBuilderSubclass) do |nested_fields| + klass = nested_fields.class + '' + end + end + + assert_equal LabelledFormBuilderSubclass, klass + end + def test_form_for_with_html_options_adds_options_to_form_tag form_for(:post, @post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end expected = "<form action=\"http://www.example.com\" class=\"some_class\" id=\"some_form\" method=\"post\"></form>" diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 0c8af60aa4..c713b8da8e 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -266,11 +266,18 @@ class FormTagHelperTest < ActionView::TestCase def test_submit_tag_with_confirmation assert_dom_equal( - %(<input name='commit' type='submit' value='Save' onclick="return confirm('Are you sure?');"/>), + %(<input name='commit' type='submit' value='Save' onclick="if (!confirm('Are you sure?')) return false; return true;"/>), submit_tag("Save", :confirm => "Are you sure?") ) end - + + def test_submit_tag_with_confirmation_and_with_disable_with + assert_dom_equal( + %(<input name="commit" type="submit" value="Save" onclick="if (!confirm('Are you sure?')) return false; if (window.hiddenCommit) { window.hiddenCommit.setAttribute('value', this.value); }else { hiddenCommit = this.cloneNode(false);hiddenCommit.setAttribute('type', 'hidden');this.form.appendChild(hiddenCommit); }this.setAttribute('originalValue', this.value);this.disabled = true;this.value='Saving...';result = (this.form.onsubmit ? (this.form.onsubmit() ? this.form.submit() : false) : this.form.submit());if (result == false) { this.value = this.getAttribute('originalValue');this.disabled = false; }return result;" />), + submit_tag("Save", :disable_with => "Saving...", :confirm => "Are you sure?") + ) + end + def test_image_submit_tag_with_confirmation assert_dom_equal( %(<input type="image" src="/images/save.gif" onclick="return confirm('Are you sure?');"/>), diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index d41111127b..d2fb24e36e 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -45,11 +45,6 @@ class JavaScriptHelperTest < ActionView::TestCase link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') end - def test_link_to_function_with_href - assert_dom_equal %(<a href="http://example.com/" onclick="alert('Hello world!'); return false;">Greeting</a>), - link_to_function("Greeting", "alert('Hello world!')", :href => 'http://example.com/') - end - def test_button_to_function assert_dom_equal %(<input type="button" onclick="alert('Hello world!');" value="Greeting" />), button_to_function("Greeting", "alert('Hello world!')") diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 9c9f54936c..29cb60fd73 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -4,6 +4,7 @@ class NumberHelperTest < ActionView::TestCase tests ActionView::Helpers::NumberHelper def test_number_to_phone + assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) diff --git a/actionpack/test/template/output_buffer_test.rb b/actionpack/test/template/output_buffer_test.rb new file mode 100644 index 0000000000..6d8eab63dc --- /dev/null +++ b/actionpack/test/template/output_buffer_test.rb @@ -0,0 +1,35 @@ +require 'abstract_unit' + +class OutputBufferTest < ActionController::TestCase + class TestController < ActionController::Base + def index + render :text => 'foo' + end + end + + tests TestController + + def test_flush_output_buffer + # Start with the default body parts + get :index + assert_equal ['foo'], @response.body_parts + assert_nil @response.template.output_buffer + + # Nil output buffer is skipped + @response.template.flush_output_buffer + assert_nil @response.template.output_buffer + assert_equal ['foo'], @response.body_parts + + # Empty output buffer is skipped + @response.template.output_buffer = '' + @response.template.flush_output_buffer + assert_equal '', @response.template.output_buffer + assert_equal ['foo'], @response.body_parts + + # Flushing appends the output buffer to the body parts + @response.template.output_buffer = 'bar' + @response.template.flush_output_buffer + assert_equal '', @response.template.output_buffer + assert_equal ['foo', 'bar'], @response.body_parts + end +end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 107c625e32..9adf053b09 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -145,6 +145,10 @@ module RenderTestCases assert_equal File.expand_path("#{FIXTURE_LOAD_PATH}/test/_raise.html.erb"), e.file_name end + def test_render_object + assert_equal "Hello: david", @view.render(:partial => "test/customer", :object => Customer.new("david")) + end + def test_render_partial_collection assert_equal "Hello: davidHello: mary", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]) end @@ -224,6 +228,16 @@ module RenderTestCases assert_equal 'source: Hello, <%= name %>!; locals: {:name=>"Josh"}', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end + def test_render_ignores_templates_with_malformed_template_handlers + %w(malformed malformed.erb malformed.html.erb malformed.en.html.erb).each do |name| + assert_raise(ActionView::MissingTemplate) { @view.render(:file => "test/malformed/#{name}") } + end + end + + def test_template_with_malformed_template_handler_is_reachable_through_its_exact_filename + assert_equal "Don't render me!", @view.render(:file => 'test/malformed/malformed.html.erb~') + end + def test_render_with_layout assert_equal %(<title></title>\nHello world!\n), @view.render(:file => "test/hello_world.erb", :layout => "layouts/yield") diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index 564845779f..a370f1458f 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -375,6 +375,12 @@ class TextHelperTest < ActionView::TestCase assert_equal "{link: #{link3_result}}", auto_link("{link: #{link3_raw}}") end + def test_auto_link_in_tags + link_raw = 'http://www.rubyonrails.org/images/rails.png' + link_result = %Q(<img src="#{link_raw}" />) + assert_equal link_result, auto_link(link_result) + end + def test_auto_link_at_eol url1 = "http://api.rubyonrails.com/Foo.html" url2 = "http://www.ruby-doc.org/core/Bar.html" diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index e7799fb204..5900709d81 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -220,7 +220,7 @@ class UrlHelperTest < ActionView::TestCase end def test_link_tag_using_post_javascript_and_popup - assert_raises(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } + assert_raise(ActionView::ActionViewError) { link_to("Hello", "http://www.example.com", :popup => true, :method => :post, :confirm => "Are you serious?") } end def test_link_tag_using_block_in_erb |