diff options
author | Ryan Bigg <radarlistener@gmail.com> | 2009-04-04 16:13:18 +1000 |
---|---|---|
committer | Ryan Bigg <radarlistener@gmail.com> | 2009-04-04 16:13:18 +1000 |
commit | e878c3e44a8dba19c1188b636e25ec1bc2165116 (patch) | |
tree | 3d06b83492653b6b73422cbf37014fc5c89e9685 /actionpack | |
parent | ab55ddcc4c5cf31bcaf7720b52dc55d6d54cb150 (diff) | |
parent | 9e9469e83f6144310115124cdecc5cb65db5128e (diff) | |
download | rails-e878c3e44a8dba19c1188b636e25ec1bc2165116.tar.gz rails-e878c3e44a8dba19c1188b636e25ec1bc2165116.tar.bz2 rails-e878c3e44a8dba19c1188b636e25ec1bc2165116.zip |
Merge branch 'master' of git@github.com:lifo/docrails
* 'master' of git@github.com:lifo/docrails: (319 commits)
deletes screencast promo in prologue, its proper place is the References section
Typo fix
list -> index
in caching guide, RESTifies some examples, revised conventions here and there
Tech edit of caching guide from Gregg Pollack
Fix typo in comment: hide_actions -> hide_action
Fix two typos in a comment in config/initializers/backtrace_silencers.rb
With -> with in a title
Clear up a little confusing wording in Routing Guide.
copyedited minor details in the rack on rails guide
remove piece of UrlWriter documentation claiming that you can access named routes as its class methods
Add note about change to session options
TRUNCATE is also a MySQL DDL statement, so document this is a possible caveat when using transactions and savepoints.
Improve documentation for ActiveResource::Validations, fix typos
Fix typos in ActiveResource::Base documentation, use present tense, reword confusing sentences
Update ActiveResource::Connection documentation to use present tense
Fix typos in Active Resource README
Fix a small typo
ensure authors get warnings about broken links, and ensure end users don't
in guides generator, warn about duplicate header IDs only if WARN_DUPLICATE_HEADERS
...
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 |