diff options
Diffstat (limited to 'actionpack')
127 files changed, 1232 insertions, 1029 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 57b8b5dfc9..2cb7af05e5 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,5 +1,72 @@ ## Rails 4.0.0 (unreleased) ## +* Fixed json params parsing regression for non-object JSON content. + + *Dylan Smith* + +* Extract `ActionDispatch::PerformanceTest` into https://github.com/rails/rails-perftest + You can add the gem to your Gemfile to keep using performance tests. + + gem 'rails-perftest' + + *Yves Senn* + +* Added view_cache_dependency API for declaring dependencies that affect + cache digest computation. + + *Jamis Buck* + +* `image_submit_tag` will set `alt` attribute from image source if not + specified. + + *Nihad Abbasov* + +* Do not generate local variables for partials without object or collection. + Previously rendering a partial without giving `:object` or `:collection` + would generate a local variable with the partial name by default. + + *Carlos Antonio da Silva* + +* Return the last valid, non-private IP address from the X-Forwarded-For, + Client-IP and Remote-Addr headers, in that order. Document the rationale + for that decision, and describe the options that can be passed to the + RemoteIp middleware to change it. + Fix #7979 + + *André Arko*, *Steve Klabnik*, *Alexey Gaziev* + +* Do not append second slash to `root_url` when using `trailing_slash: true` + Fix #8700 + + Example: + # before + root_url # => http://test.host// + + # after + root_url # => http://test.host/ + + *Yves Senn* + +* Allow to toggle dumps on error pages. + + *Gosha Arinich* + +* Fix a bug in `content_tag_for` that prevents it from working without a block. + + *Jasl* + +* Change the stylesheet of exception pages for development mode. + Additionally display also the line of code and fragment that raised + the exception in all exceptions pages. + + *Guillermo Iguaran + Jorge Cuadrado* + +* Do not append `charset=` parameter when `head` is called with a + `:content_type` option. + Fix #8661. + + *Yves Senn* + * Added `Mime::NullType` class. This allows to use html?, xml?, json?..etc when the `format` of `request` is unknown, without raise an exception. @@ -105,7 +172,7 @@ *Drew Ulmer* -* No sort Hash options in `grouped_options_for_select`. *Sergey Kojin* +* Do not sort Hash options in `grouped_options_for_select`. *Sergey Kojin* * Accept symbols as `send_data :disposition` value *Elia Schito* @@ -153,14 +220,15 @@ * Render every partial with a new `ActionView::PartialRenderer`. This resolves issues when rendering nested partials. - Fix #8197 + Fix #8197. *Yves Senn* * Introduce `ActionView::Template::Handlers::ERB.escape_whitelist`. This is a list of mime types where template text is not html escaped by default. It prevents `Jack & Joe` from rendering as `Jack & Joe` for the whitelisted mime types. The default whitelist - contains text/plain. Fix #7976 + contains `text/plain`. + Fix #7976. *Joost Baaij* @@ -176,12 +244,13 @@ check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> - Fix #8108 + Fix #8108. *Daniel Fox, Grant Hutchins & Trace Wax* * `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's - returned value if any. Fix #8086 + returned value if any. + Fix #8086. *Nikita Afanasenko* @@ -190,21 +259,21 @@ *Pavel Nikitin* -* Only non-js/css under app/assets path will be included in default config.assets.precompile. +* Only non-js/css under `app/assets` path will be included in default `config.assets.precompile`. *Josh Peek* -* Remove support for the RAILS_ASSET_ID environment configuration +* Remove support for the `RAILS_ASSET_ID` environment configuration (no longer needed now that we have the asset pipeline). *Josh Peek* -* Remove old asset_path configuration (no longer needed now that we have the asset pipeline). +* Remove old `asset_path` configuration (no longer needed now that we have the asset pipeline). *Josh Peek* * `assert_template` can be used to assert on the same template with different locals - Fix #3675 + Fix #3675. *Yves Senn* @@ -212,10 +281,10 @@ *Josh Peek* -* Accept :remote as symbolic option for `link_to` helper. *Riley Lynch* +* Accept `:remote` as symbolic option for `link_to` helper. *Riley Lynch* * Warn when the `:locals` option is passed to `assert_template` outside of a view test case - Fix #3415 + Fix #3415. *Yves Senn* @@ -235,24 +304,24 @@ *Francesco Rodriguez* -* Failsafe exception returns text/plain. *Steve Klabnik* +* Failsafe exception returns `text/plain`. *Steve Klabnik* * Remove `rack-cache` dependency from Action Pack and declare it on Gemfile *Guillermo Iguaran* -* Rename internal variables on ActionController::TemplateAssertions to prevent - naming collisions. @partials, @templates and @layouts are now prefixed with an underscore. - Fix #7459 +* Rename internal variables on `ActionController::TemplateAssertions` to prevent + naming collisions. `@partials`, `@templates` and `@layouts` are now prefixed with an underscore. + Fix #7459. *Yves Senn* -* `resource` and `resources` don't modify the passed options hash - Fix #7777 +* `resource` and `resources` don't modify the passed options hash. + Fix #7777. *Yves Senn* -* Precompiled assets include aliases from foo.js to foo/index.js and vice versa. +* Precompiled assets include aliases from `foo.js` to `foo/index.js` and vice versa. # Precompiles phone-<digest>.css and aliases phone/index.css to phone.css. config.assets.precompile = [ 'phone.css' ] @@ -290,8 +359,8 @@ *Nihad Abbasov* -* Deprecate Mime::Type#verify_request? and Mime::Type.browser_generated_types, - since they are no longer used inside of Rails, they will be removed in Rails 4.1 +* Deprecate `Mime::Type#verify_request?` and `Mime::Type.browser_generated_types`, + since they are no longer used inside of Rails, they will be removed in Rails 4.1. *Michael Grosser* @@ -307,11 +376,12 @@ * Remove Integration between `attr_accessible`/`attr_protected` and `ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned - by the class method attribute_names + by the class method `attribute_names`. *Guillermo Iguaran* -* Fix #7646, the log now displays the correct status code when an exception is raised. +* Log now displays the correct status code when an exception is raised. + Fix #7646. *Yves Senn* @@ -349,7 +419,7 @@ *Sergey Nartimov* -* Add .ruby template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* +* Add `.ruby` template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran* * Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: @@ -370,7 +440,7 @@ end end -* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the cache_digests plugin) *DHH* +* Add automatic template digests to all `CacheHelper#cache` calls (originally spiked in the `cache_digests` plugin) *DHH* * When building a URL fails, add missing keys provided by Journey. Failed URL generation now returns a 500 status instead of a 404. @@ -464,7 +534,7 @@ *Egor Homakov* -* Allow data attributes to be set as a first-level option for form_for, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH* +* Allow data attributes to be set as a first-level option for `form_for`, so you can write `form_for @record, data: { behavior: 'autosave' }` instead of `form_for @record, html: { data: { behavior: 'autosave' } }` *DHH* * Deprecate `button_to_function` and `link_to_function` helpers. @@ -520,7 +590,9 @@ *Carlos Galdino + Rafael Mendonça França* -* Show routes in exception page while debugging a `RoutingError` in development. *Richard Schneeman and Mattt Thompson* +* Show routes in exception page while debugging a `RoutingError` in development. + + *Richard Schneeman + Mattt Thompson + Yves Senn* * Add `ActionController::Flash.add_flash_types` method to allow people to register their own flash types. e.g.: @@ -570,7 +642,7 @@ *Sergey Nartimov* -* change a way of ordering helpers from several directories. Previously, +* Change a way of ordering helpers from several directories. Previously, when loading helpers from multiple paths, all of the helpers files were gathered into one array an then they were sorted. Helpers from different directories should not be mixed before loading them to make loading more @@ -606,7 +678,7 @@ * Add `time_field` and `time_field_tag` helpers which render an `input[type="time"]` tag. *Alex Soulim* -* Removed old text_helper apis for highlight, excerpt and word_wrap *Jeremy Walker* +* Removed old text helper apis from `highlight`, `excerpt` and `word_wrap`. *Jeremy Walker* * Templates without a handler extension now raises a deprecation warning but still defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik* @@ -617,7 +689,7 @@ * Remove `:mouseover` option from `image_tag` helper. *Rafael Mendonça França* -* The `select` method (select tag) forces :include_blank if `required` is true and +* The `select` method (select tag) forces `:include_blank` if `required` is true and `display size` is one and `multiple` is not true. *Angelo Capilleri* * Copy literal route constraints to defaults so that url generation know about them. @@ -639,7 +711,7 @@ * Make current object and counter (when it applies) variables accessible when rendering templates with :object / :collection. *Carlos Antonio da Silva* -* JSONP now uses mimetype text/javascript instead of application/json. *omjokine* +* JSONP now uses mimetype `text/javascript` instead of `application/json`. *omjokine* * Allow to lazy load `default_form_builder` by passing a `String` instead of a constant. *Piotr Sarnacki* @@ -652,14 +724,14 @@ * Add `index` method to FormBuilder class. *Jorge Bejar* -* Remove the leading \n added by textarea on assert_select. *Santiago Pastorino* +* Remove the leading \n added by textarea on `assert_select`. *Santiago Pastorino* * Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` to `false`. This change breaks remote forms that need to work also without javascript, so if you need such behavior, you can either set it to `true` or explicitly pass - `authenticity_token: true` in form options + `authenticity_token: true` in form options. -* Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* +* Added `ActionDispatch::SSL` middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* * Add `include_hidden` option to select tag. With `include_hidden: false` select with `multiple` attribute doesn't generate hidden input with blank value. *Vasiliy Ermolovich* diff --git a/actionpack/MIT-LICENSE b/actionpack/MIT-LICENSE index 810daf856c..5c668d9624 100644 --- a/actionpack/MIT-LICENSE +++ b/actionpack/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2012 David Heinemeier Hansson +Copyright (c) 2004-2013 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index ccd0193515..29a7fcf0f0 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -10,7 +10,7 @@ It consists of several modules: * Action Dispatch, which parses information about the web request, handles routing as defined by the user, and does advanced processing related to HTTP - such as MIME-type negotiation, decoding parameters in POST/PUT bodies, + such as MIME-type negotiation, decoding parameters in POST, PATCH, or PUT bodies, handling HTTP caching logic, cookies and sessions. * Action Controller, which provides a base controller class that can be diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index c65870cac6..f00fc46b2e 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -21,7 +21,7 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version s.add_dependency 'builder', '~> 3.1.0' - s.add_dependency 'rack', '~> 1.4.1' + s.add_dependency 'rack', '~> 1.4.3' s.add_dependency 'rack-test', '~> 0.6.1' s.add_dependency 'erubis', '~> 2.7.0' diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 73799e8085..91864f2a35 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -209,7 +209,7 @@ module AbstractController _write_layout_method end - delegate :_layout_conditions, :to => "self.class" + delegate :_layout_conditions, to: :class module ClassMethods def inherited(klass) # :nodoc: diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 1a13d7af29..9cacb3862b 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -42,7 +42,6 @@ module ActionController autoload :Integration, 'action_controller/deprecated/integration_test' autoload :IntegrationTest, 'action_controller/deprecated/integration_test' - autoload :PerformanceTest, 'action_controller/deprecated/performance_test' autoload :Routing, 'action_controller/deprecated' autoload :TestCase, 'action_controller/test_case' autoload :TemplateAssertions, 'action_controller/test_case' diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 2892e093af..cf2cda039d 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -70,12 +70,26 @@ module ActionController config_accessor :perform_caching self.perform_caching = true if perform_caching.nil? + + class_attribute :_view_cache_dependencies + self._view_cache_dependencies = [] + helper_method :view_cache_dependencies if respond_to?(:helper_method) + end + + module ClassMethods + def view_cache_dependency(&dependency) + self._view_cache_dependencies += [dependency] + end end def caching_allowed? request.get? && response.status == 200 end + def view_cache_dependencies + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact + end + protected # Convenience accessor. def cache(key, options = {}, &block) diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb deleted file mode 100644 index c7ba5a2fe7..0000000000 --- a/actionpack/lib/action_controller/deprecated/performance_test.rb +++ /dev/null @@ -1,3 +0,0 @@ -ActionController::PerformanceTest = ActionDispatch::PerformanceTest - -ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.' diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 3f9b382a11..eddee08545 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/class/attribute' - module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index bbace49fd9..8237db15ca 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -31,6 +31,7 @@ module ActionController if include_content?(self.status) self.content_type = content_type || (Mime[formats.first] if formats) + self.response.charset = false if self.response self.response_body = " " else headers.delete('Content-Type') diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 896238b7dc..e295002b16 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -228,7 +228,7 @@ module ActionController end def decode_credentials(header) - HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair| + ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair| key, value = pair.split('=', 2) [key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')] end] diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 6bf306ac5b..d04fbae150 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -82,7 +82,7 @@ module ActionController #:nodoc: # (by name) if it does not already exist, without web-services, it might look like this: # # def create - # @company = Company.find_or_create_by_name(params[:company][:name]) + # @company = Company.find_or_create_by(name: params[:company][:name]) # @person = @company.people.create(params[:person]) # # redirect_to(person_list_url) @@ -92,7 +92,7 @@ module ActionController #:nodoc: # # def create # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) + # @company = Company.find_or_create_by(name: company[:name]) # @person = @company.people.create(params[:person]) # # respond_to do |format| @@ -120,7 +120,7 @@ module ActionController #:nodoc: # Note, however, the extra bit at the top of that action: # # company = params[:person].delete(:company) - # @company = Company.find_or_create_by_name(company[:name]) + # @company = Company.find_or_create_by(name: company[:name]) # # This is because the incoming XML document (if a web-service request is in process) can only contain a # single root-node. So, we have to rearrange things so that the request looks like this (url-encoded): @@ -227,7 +227,7 @@ module ActionController #:nodoc: # i.e. its +show+ action. # 2. If there are validation errors, the response # renders a default action, which is <tt>:new</tt> for a - # +post+ request or <tt>:edit</tt> for +put+. + # +post+ request or <tt>:edit</tt> for +patch+ or +put+. # Thus an example like this - # # respond_to :html, :xml @@ -320,7 +320,7 @@ module ActionController #:nodoc: # 2. <tt>:action</tt> - overwrites the default render action used after an # unsuccessful html +post+ request. def respond_with(*resources, &block) - raise "In order to use respond_with, first you need to declare the formats your " << + raise "In order to use respond_with, first you need to declare the formats your " \ "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? if collector = retrieve_collector_from_mimes(&block) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 091facfd8d..13ec55fe92 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -32,7 +32,7 @@ module ActionController # redirect_to :back # redirect_to proc { edit_post_url(@post) } # - # The redirection happens as a "302 Moved" header unless otherwise specified. + # The redirection happens as a "302 Found" header unless otherwise specified. # # redirect_to post_url(@post), status: :found # redirect_to action: 'atom', status: :moved_permanently diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0b3c438ec2..73e9b5660d 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -26,7 +26,7 @@ module ActionController #:nodoc: # # class PostsController # def index - # @posts = Post.scoped + # @posts = Post.all # render stream: true # end # end @@ -51,9 +51,9 @@ module ActionController #:nodoc: # # def dashboard # # Allow lazy execution of the queries - # @posts = Post.scoped - # @pages = Page.scoped - # @articles = Article.scoped + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all # render stream: true # end # diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 8faa5f8a13..941b6e210f 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' @@ -41,11 +40,11 @@ module ActionController # permitted.class # => ActionController::Parameters # permitted.permitted? # => true # - # Person.first.update_attributes!(permitted) + # Person.first.update!(permitted) # # => #<Person id: 1, name: "Francesco", age: 22, role: "user"> # # It provides a +permit_all_parameters+ option that controls the top-level - # behaviour of new instances. If it's +true+, all the parameters will be + # behavior of new instances. If it's +true+, all the parameters will be # permitted by default. The default value for +permit_all_parameters+ # option is +false+. # @@ -329,7 +328,7 @@ module ActionController # # into a 400 Bad Request reply. # def update # redirect_to current_account.people.find(params[:id]).tap { |person| - # person.update_attributes!(person_params) + # person.update!(person_params) # } # end # diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index b49128c184..70e0a820c4 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,11 +1,10 @@ -require 'active_support/deprecation' require 'action_view/record_identifier' module ActionController module RecordIdentifier MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' + 'If you would like to use it in controllers, please include ' + - 'ActionView::RecodIdentifier module.' + 'ActionView::RecordIdentifier module.' def dom_id(record, prefix = nil) ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE) diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 7b48870090..331d15d403 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -360,13 +360,6 @@ module ActionController # # assert_redirected_to page_url(title: 'foo') class TestCase < ActiveSupport::TestCase - - # Use AC::TestCase for the base class when describing a controller - register_spec_type(self) do |desc| - Class === desc && desc < ActionController::Metal - end - register_spec_type(/Controller( ?Test)?\z/i, self) - module Behavior extend ActiveSupport::Concern include ActionDispatch::TestProcess diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 938161dc69..9ab048b756 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2012 David Heinemeier Hansson +# Copyright (c) 2004-2013 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -97,7 +97,6 @@ module ActionDispatch autoload :Assertions autoload :Integration autoload :IntegrationTest, 'action_dispatch/testing/integration' - autoload :PerformanceTest autoload :TestProcess autoload :TestRequest autoload :TestResponse diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 02ab49b44e..289e204ac8 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' +require 'action_dispatch/http/parameter_filter' module ActionDispatch module Http diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 6610315da7..25edd196c3 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -72,7 +72,7 @@ module ActionDispatch end end - # Convert nested Hash to HashWithIndifferentAccess + # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess def normalize_parameters(value) case value when Hash diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index d60c8775af..7b04d6e851 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -1,12 +1,16 @@ -require 'tempfile' require 'stringio' -require 'strscan' -require 'active_support/core_ext/hash/indifferent_access' -require 'active_support/core_ext/string/access' require 'active_support/inflector' require 'action_dispatch/http/headers' require 'action_controller/metal/exceptions' +require 'rack/request' +require 'action_dispatch/http/cache' +require 'action_dispatch/http/mime_negotiation' +require 'action_dispatch/http/parameters' +require 'action_dispatch/http/filter_parameters' +require 'action_dispatch/http/upload' +require 'action_dispatch/http/url' +require 'active_support/core_ext/array/conversions' module ActionDispatch class Request < Rack::Request @@ -280,15 +284,14 @@ module ActionDispatch LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end - protected - # Remove nils from the params hash def deep_munge(hash) - hash.each_value do |v| + hash.each do |k, v| case v when Array v.grep(Hash) { |x| deep_munge(x) } v.compact! + hash[k] = nil if v.empty? when Hash deep_munge(v) end @@ -297,6 +300,8 @@ module ActionDispatch hash end + protected + def parse_query(qs) deep_munge(super) end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 0f808ac9cf..91cf4784db 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -260,14 +260,18 @@ module ActionDispatch # :nodoc: return if headers[CONTENT_TYPE].present? @content_type ||= Mime::HTML - @charset ||= self.class.default_charset + @charset ||= self.class.default_charset unless @charset == false type = @content_type.to_s.dup - type << "; charset=#{@charset}" unless @sending_file + type << "; charset=#{@charset}" if append_charset? headers[CONTENT_TYPE] = type end + def append_charset? + !@sending_file && @charset != false + end + def rack_response(status, header) assign_default_content_type_and_charset!(header) handle_conditional_get! diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 79437d6e85..8a97248eb3 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -19,7 +19,7 @@ module ActionDispatch # its interface is available directly. attr_accessor :tempfile - # TODO. + # A string with the headers of the multipart request. attr_accessor :headers def initialize(hash) # :nodoc: @@ -75,7 +75,7 @@ module ActionDispatch end module Upload # :nodoc: - # Convert nested Hash to HashWithIndifferentAccess and replace + # Convert nested Hash to ActiveSupport::HashWithIndifferentAccess and replace # file upload hash with UploadedFile objects def normalize_parameters(value) if Hash === value && value.has_key?(:tempfile) diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index bced7d84c0..43f26d696d 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -32,7 +32,11 @@ module ActionDispatch params.reject! { |_,v| v.to_param.nil? } result = build_host_url(options) - result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) + if options[:trailing_slash] && !path.ends_with?('/') + result << path.sub(/(\?|\z)/) { "/" + $& } + else + result << path + end result << "?#{params.to_query}" unless params.empty? result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor] result diff --git a/actionpack/lib/action_dispatch/journey/formatter.rb b/actionpack/lib/action_dispatch/journey/formatter.rb index 4a344f71af..cf755bfbeb 100644 --- a/actionpack/lib/action_dispatch/journey/formatter.rb +++ b/actionpack/lib/action_dispatch/journey/formatter.rb @@ -37,19 +37,16 @@ module ActionDispatch private def extract_parameterized_parts(route, options, recall, parameterize = nil) - constraints = recall.merge(options) - data = constraints.dup + parameterized_parts = recall.merge(options) keys_to_keep = route.parts.reverse.drop_while { |part| !options.key?(part) || (options[part] || recall[part]).nil? } | route.required_parts - (data.keys - keys_to_keep).each do |bad_key| - data.delete(bad_key) + (parameterized_parts.keys - keys_to_keep).each do |bad_key| + parameterized_parts.delete(bad_key) end - parameterized_parts = data.dup - if parameterize parameterized_parts.each do |k, v| parameterized_parts[k] = parameterize.call(k, v) diff --git a/actionpack/lib/action_dispatch/journey/route.rb b/actionpack/lib/action_dispatch/journey/route.rb index d18efd863a..d3988cf31e 100644 --- a/actionpack/lib/action_dispatch/journey/route.rb +++ b/actionpack/lib/action_dispatch/journey/route.rb @@ -30,11 +30,11 @@ module ActionDispatch end def ast - return @decorated_ast if @decorated_ast - - @decorated_ast = path.ast - @decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } - @decorated_ast + @decorated_ast ||= begin + decorated_ast = path.ast + decorated_ast.grep(Nodes::Terminal).each { |n| n.memo = self } + decorated_ast + end end def requirements # :nodoc: @@ -45,7 +45,7 @@ module ActionDispatch end def segments - @path.names + path.names end def required_keys diff --git a/actionpack/lib/action_dispatch/journey/routes.rb b/actionpack/lib/action_dispatch/journey/routes.rb index 32829a1f20..a99d6d0d6a 100644 --- a/actionpack/lib/action_dispatch/journey/routes.rb +++ b/actionpack/lib/action_dispatch/journey/routes.rb @@ -16,12 +16,12 @@ module ActionDispatch end def length - @routes.length + routes.length end alias :size :length def last - @routes.last + routes.last end def each(&block) @@ -33,24 +33,23 @@ module ActionDispatch end def partitioned_routes - @partitioned_routes ||= routes.partition { |r| - r.path.anchored && r.ast.grep(Nodes::Symbol).all? { |n| n.default_regexp? } - } + @partitioned_routes ||= routes.partition do |r| + r.path.anchored && r.ast.grep(Nodes::Symbol).all?(&:default_regexp?) + end end def ast - return @ast if @ast - return if partitioned_routes.first.empty? - - asts = partitioned_routes.first.map { |r| r.ast } - @ast = Nodes::Or.new(asts) + @ast ||= begin + asts = partitioned_routes.first.map(&:ast) + Nodes::Or.new(asts) unless asts.empty? + end end def simulator - return @simulator if @simulator - - gtg = GTG::Builder.new(ast).transition_table - @simulator = GTG::Simulator.new(gtg) + @simulator ||= begin + gtg = GTG::Builder.new(ast).transition_table + GTG::Simulator.new(gtg) + end end # Add a route to the routing table. diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index 2f148752cb..6ecbb03784 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -15,7 +15,7 @@ module ActionDispatch # being written will be sent out with the response. Reading a cookie does not get # the cookie object itself back, just the value it holds. # - # Examples for writing: + # Examples of writing: # # # Sets a simple session cookie. # # This cookie will be deleted when the user's browser is closed. @@ -38,7 +38,7 @@ module ActionDispatch # # You can also chain these methods: # cookies.permanent.signed[:login] = "XJ-122" # - # Examples for reading: + # Examples of reading: # # cookies[:user_name] # => "david" # cookies.size # => 2 @@ -87,6 +87,9 @@ module ActionDispatch ENCRYPTED_SIGNED_COOKIE_SALT = "action_dispatch.encrypted_signed_cookie_salt".freeze TOKEN_KEY = "action_dispatch.secret_token".freeze + # Cookies can typically store 4096 bytes. + MAX_COOKIE_SIZE = 4096 + # Raised when storing more than 4K of session data. CookieOverflow = Class.new StandardError @@ -293,13 +296,17 @@ module ActionDispatch end end - class PermanentCookieJar < CookieJar #:nodoc: + class PermanentCookieJar #:nodoc: def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @key_generator = key_generator @options = options end + def [](key) + @parent_jar[name.to_s] + end + def []=(key, options) if options.is_a?(Hash) options.symbolize_keys! @@ -311,14 +318,25 @@ module ActionDispatch @parent_jar[key] = options end + def permanent + @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) + end + + def signed + @signed ||= SignedCookieJar.new(self, @key_generator, @options) + end + + def encrypted + @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) + end + def method_missing(method, *arguments, &block) - @parent_jar.send(method, *arguments, &block) + ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + + "You probably want to try this method over the parent CookieJar." end end - class SignedCookieJar < CookieJar #:nodoc: - MAX_COOKIE_SIZE = 4096 # Cookies can typically store 4096 bytes. - + class SignedCookieJar #:nodoc: def initialize(parent_jar, key_generator, options = {}) @parent_jar = parent_jar @options = options @@ -346,12 +364,25 @@ module ActionDispatch @parent_jar[key] = options end + def permanent + @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) + end + + def signed + @signed ||= SignedCookieJar.new(self, @key_generator, @options) + end + + def encrypted + @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) + end + def method_missing(method, *arguments, &block) - @parent_jar.send(method, *arguments, &block) + ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + + "You probably want to try this method over the parent CookieJar." end end - class EncryptedCookieJar < SignedCookieJar #:nodoc: + class EncryptedCookieJar #:nodoc: def initialize(parent_jar, key_generator, options = {}) if ActiveSupport::DummyKeyGenerator === key_generator raise "Encrypted Cookies must be used in conjunction with config.secret_key_base." + @@ -365,8 +396,8 @@ module ActionDispatch @encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret) end - def [](name) - if encrypted_message = @parent_jar[name] + def [](key) + if encrypted_message = @parent_jar[key] @encryptor.decrypt_and_verify(encrypted_message) end rescue ActiveSupport::MessageVerifier::InvalidSignature, @@ -385,6 +416,23 @@ module ActionDispatch raise CookieOverflow if options[:value].size > MAX_COOKIE_SIZE @parent_jar[key] = options end + + def permanent + @permanent ||= PermanentCookieJar.new(self, @key_generator, @options) + end + + def signed + @signed ||= SignedCookieJar.new(self, @key_generator, @options) + end + + def encrypted + @encrypted ||= EncryptedCookieJar.new(self, @key_generator, @options) + end + + def method_missing(method, *arguments, &block) + ActiveSupport::Deprecation.warn "#{method} is deprecated with no replacement. " + + "You probably want to try this method over the parent CookieJar." + end end def initialize(app) diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 6705e531cb..64230ff1ae 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -2,7 +2,6 @@ require 'action_dispatch/http/request' require 'action_dispatch/middleware/exception_wrapper' require 'action_dispatch/routing/inspector' - module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. @@ -15,18 +14,17 @@ module ActionDispatch end def call(env) - begin - response = (_, headers, body = @app.call(env)) - - if headers['X-Cascade'] == 'pass' - body.close if body.respond_to?(:close) - raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" - end - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false + _, headers, body = response = @app.call(env) + + if headers['X-Cascade'] == 'pass' + body.close if body.respond_to?(:close) + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end - exception ? render_exception(env, exception) : response + response + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) end private @@ -42,9 +40,11 @@ module ActionDispatch :application_trace => wrapper.application_trace, :framework_trace => wrapper.framework_trace, :full_trace => wrapper.full_trace, - :routes => formatted_routes(exception) + :routes_inspector => routes_inspector(exception), + :source_extract => wrapper.source_extract, + :line_number => wrapper.line_number, + :file => wrapper.file ) - file = "rescues/#{wrapper.rescue_template}" body = template.render(:template => file, :layout => 'rescues/layout') render(wrapper.status_code, body) @@ -82,11 +82,9 @@ module ActionDispatch @stderr_logger ||= ActiveSupport::Logger.new($stderr) end - def formatted_routes(exception) - return false unless @routes_app.respond_to?(:routes) - if exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error) - inspector = ActionDispatch::Routing::RoutesInspector.new - inspector.collect_routes(@routes_app.routes.routes) + def routes_inspector(exception) + if @routes_app.respond_to?(:routes) && (exception.is_a?(ActionController::RoutingError) || exception.is_a?(ActionView::Template::Error)) + ActionDispatch::Routing::RoutesInspector.new(@routes_app.routes.routes) end end end diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index ae38c56a67..869d0aa7af 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,4 @@ require 'action_controller/metal/exceptions' -require 'active_support/core_ext/exception' require 'active_support/core_ext/class/attribute_accessors' module ActionDispatch @@ -25,7 +24,7 @@ module ActionDispatch 'ActionView::Template::Error' => 'template_error' ) - attr_reader :env, :exception + attr_reader :env, :exception, :line_number, :file def initialize(env, exception) @env = env @@ -56,6 +55,15 @@ module ActionDispatch Rack::Utils.status_code(@@rescue_responses[class_name]) end + def source_extract + if application_trace && trace = application_trace.first + file, line, _ = trace.split(":") + @file = file + @line_number = line.to_i + source_fragment(@file, @line_number) + end + end + private def original_exception(exception) @@ -81,5 +89,17 @@ module ActionDispatch def backtrace_cleaner @backtrace_cleaner ||= @env['action_dispatch.backtrace_cleaner'] end + + def source_fragment(path, line) + return unless Rails.respond_to?(:root) && Rails.root + full_path = Rails.root.join(path) + if File.exists?(full_path) + File.open(full_path, "r") do |file| + start = [line - 3, 0].max + lines = file.each_line.drop(start).take(6) + Hash[*(start+1..(lines.count+start)).zip(lines).flatten] + end + end + end end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index f24e9b8e18..7e56feb90a 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -59,12 +59,12 @@ module ActionDispatch @flash[k] end - # Convenience accessor for flash.now[:alert]= + # Convenience accessor for <tt>flash.now[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash.now[:notice]= + # Convenience accessor for <tt>flash.now[:notice]=</tt>. def notice=(message) self[:notice] = message end @@ -82,7 +82,7 @@ module ActionDispatch else new end - + flash.tap(&:sweep) end @@ -169,6 +169,14 @@ module ActionDispatch # vanish when the current action is done. # # Entries set via <tt>now</tt> are accessed the same way as standard entries: <tt>flash['my-key']</tt>. + # + # Also, brings two convenience accessors: + # + # flash.now.alert = "Beware now!" + # # Equivalent to flash.now[:alert] = "Beware now!" + # + # flash.now.notice = "Good luck now!" + # # Equivalent to flash.now[:notice] = "Good luck now!" def now @now ||= FlashNow.new(self) end @@ -199,22 +207,22 @@ module ActionDispatch @discard.replace @flashes.keys end - # Convenience accessor for flash[:alert] + # Convenience accessor for <tt>flash[:alert]</tt>. def alert self[:alert] end - # Convenience accessor for flash[:alert]= + # Convenience accessor for <tt>flash[:alert]=</tt>. def alert=(message) self[:alert] = message end - # Convenience accessor for flash[:notice] + # Convenience accessor for <tt>flash[:notice]</tt>. def notice self[:notice] end - # Convenience accessor for flash[:notice]= + # Convenience accessor for <tt>flash[:notice]=</tt>. def notice=(message) self[:notice] = message end diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 2c98ca03a8..0898ad82dd 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -47,14 +47,12 @@ module ActionDispatch when Proc strategy.call(request.raw_post) when :xml_simple, :xml_node - data = Hash.from_xml(request.raw_post) || {} + data = request.deep_munge(Hash.from_xml(request.body.read) || {}) data.with_indifferent_access - when :yaml - YAML.load(request.raw_post) when :json - data = ActiveSupport::JSON.decode(request.raw_post) + data = ActiveSupport::JSON.decode(request.body) data = {:_json => data} unless data.is_a?(Hash) - data.with_indifferent_access + request.deep_munge(data).with_indifferent_access else false end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 5abf8f2802..4e36c9bb49 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -1,23 +1,59 @@ module ActionDispatch + # This middleware calculates the IP address of the remote client that is + # making the request. It does this by checking various headers that could + # contain the address, and then picking the last-set address that is not + # on the list of trusted IPs. This follows the precendent set by e.g. + # {the Tomcat server}[https://issues.apache.org/bugzilla/show_bug.cgi?id=50453], + # with {reasoning explained at length}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection] + # by @gingerlime. A more detailed explanation of the algorithm is given + # at GetIp#calculate_ip. + # + # Some Rack servers concatenate repeated headers, like {HTTP RFC 2616}[http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2] + # requires. Some Rack servers simply drop preceeding headers, and only report + # the value that was {given in the last header}[http://andre.arko.net/2011/12/26/repeated-headers-and-ruby-web-servers]. + # If you are behind multiple proxy servers (like Nginx to HAProxy to Unicorn) + # then you should test your Rack server to make sure your data is good. + # + # IF YOU DON'T USE A PROXY, THIS MAKES YOU VULNERABLE TO IP SPOOFING. + # This middleware assumes that there is at least one proxy sitting around + # and setting headers with the client's remote IP address. If you don't use + # a proxy, because you are hosted on e.g. Heroku without SSL, any client can + # claim to have any IP address by setting the X-Forwarded-For header. If you + # care about that, then you need to explicitly drop or ignore those headers + # sometime before this middleware runs. class RemoteIp - class IpSpoofAttackError < StandardError ; end + class IpSpoofAttackError < StandardError; end - # IP addresses that are "trusted proxies" that can be stripped from - # the comma-delimited list in the X-Forwarded-For header. See also: - # http://en.wikipedia.org/wiki/Private_network#Private_IPv4_address_spaces - # http://en.wikipedia.org/wiki/Private_network#Private_IPv6_addresses. + # The default trusted IPs list simply includes IP addresses that are + # guaranteed by the IP specification to be private addresses. Those will + # not be the ultimate client IP in production, and so are discarded. See + # http://en.wikipedia.org/wiki/Private_network for details. TRUSTED_PROXIES = %r{ - ^127\.0\.0\.1$ | # localhost - ^::1$ | - ^(10 | # private IP 10.x.x.x - 172\.(1[6-9]|2[0-9]|3[0-1]) | # private IP in the range 172.16.0.0 .. 172.31.255.255 - 192\.168 | # private IP 192.168.x.x - fc00:: # private IP fc00 - )\. + ^127\.0\.0\.1$ | # localhost IPv4 + ^::1$ | # localhost IPv6 + ^fc00: | # private IPv6 range fc00 + ^10\. | # private IPv4 range 10.x.x.x + ^172\.(1[6-9]|2[0-9]|3[0-1])\.| # private IPv4 range 172.16.0.0 .. 172.31.255.255 + ^192\.168\. # private IPv4 range 192.168.x.x }x attr_reader :check_ip, :proxies + # Create a new +RemoteIp+ middleware instance. + # + # The +check_ip_spoofing+ option is on by default. When on, an exception + # is raised if it looks like the client is trying to lie about its own IP + # address. It makes sense to turn off this check on sites aimed at non-IP + # clients (like WAP devices), or behind proxies that set headers in an + # incorrect or confusing way (like AWS ELB). + # + # The +custom_trusted+ argument can take a regex, which will be used + # instead of +TRUSTED_PROXIES+, or a string, which will be used in addition + # to +TRUSTED_PROXIES+. Any proxy setup will put the value you want in the + # middle (or at the beginning) of the X-Forwarded-For list, with your proxy + # servers after it. If your proxies aren't removed, pass them in via the + # +custom_trusted+ parameter. That way, the middleware will ignore those + # IP addresses, and return the one that you want. def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app @check_ip = check_ip_spoofing @@ -31,15 +67,23 @@ module ActionDispatch end end + # Since the IP address may not be needed, we store the object here + # without calculating the IP to keep from slowing down the majority of + # requests. For those requests that do need to know the IP, the + # GetIp#calculate_ip method will calculate the memoized client IP address. def call(env) env["action_dispatch.remote_ip"] = GetIp.new(env, self) @app.call(env) end + # The GetIp class exists as a way to defer processing of the request data + # into an actual IP address. If the ActionDispatch::Request#remote_ip method + # is called, this class will calculate the value and then memoize it. class GetIp - # IP v4 and v6 (with compression) validation regexp - # https://gist.github.com/1289635 + # This constant contains a regular expression that validates every known + # form of IP v4 and v6 address, with or without abbreviations, adapted + # from {this gist}[https://gist.github.com/1289635]. VALID_IP = %r{ (^(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[0-9]{1,2})){3}$) | # ip v4 (^( @@ -63,62 +107,78 @@ module ActionDispatch }x def initialize(env, middleware) - @env = env - @middleware = middleware - @ip = nil + @env = env + @check_ip = middleware.check_ip + @proxies = middleware.proxies end - # Determines originating IP address. REMOTE_ADDR is the standard - # but will be wrong if the user is behind a proxy. Proxies will set - # HTTP_CLIENT_IP and/or HTTP_X_FORWARDED_FOR, so we prioritize those. - # HTTP_X_FORWARDED_FOR may be a comma-delimited list in the case of - # multiple chained proxies. The first address which is in this list - # if it's not a known proxy will be the originating IP. - # Format of HTTP_X_FORWARDED_FOR: - # client_ip, proxy_ip1, proxy_ip2... - # http://en.wikipedia.org/wiki/X-Forwarded-For + # Sort through the various IP address headers, looking for the IP most + # likely to be the address of the actual remote client making this + # request. + # + # REMOTE_ADDR will be correct if the request is made directly against the + # Ruby process, on e.g. Heroku. When the request is proxied by another + # server like HAProxy or Nginx, the IP address that made the original + # request will be put in an X-Forwarded-For header. If there are multiple + # proxies, that header may contain a list of IPs. Other proxy services + # set the Client-Ip header instead, so we check that too. + # + # As discussed in {this post about Rails IP Spoofing}[http://blog.gingerlime.com/2012/rails-ip-spoofing-vulnerabilities-and-protection/], + # while the first IP in the list is likely to be the "originating" IP, + # it could also have been set by the client maliciously. + # + # In order to find the first address that is (probably) accurate, we + # take the list of IPs, remove known and trusted proxies, and then take + # the last address left, which was presumably set by one of those proxies. def calculate_ip - client_ip = @env['HTTP_CLIENT_IP'] - forwarded_ip = ips_from('HTTP_X_FORWARDED_FOR').first - remote_addrs = ips_from('REMOTE_ADDR') - - check_ip = client_ip && @middleware.check_ip - if check_ip && forwarded_ip != client_ip + # Set by the Rack web server, this is a single value. + remote_addr = ips_from('REMOTE_ADDR').last + + # Could be a CSV list and/or repeated headers that were concatenated. + client_ips = ips_from('HTTP_CLIENT_IP').reverse + forwarded_ips = ips_from('HTTP_X_FORWARDED_FOR').reverse + + # +Client-Ip+ and +X-Forwarded-For+ should not, generally, both be set. + # If they are both set, it means that this request passed through two + # proxies with incompatible IP header conventions, and there is no way + # for us to determine which header is the right one after the fact. + # Since we have no idea, we give up and explode. + should_check_ip = @check_ip && client_ips.last + if should_check_ip && !forwarded_ips.include?(client_ips.last) # We don't know which came from the proxy, and which from the user - raise IpSpoofAttackError, "IP spoofing attack?!" \ - "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect}" \ + raise IpSpoofAttackError, "IP spoofing attack?! " + + "HTTP_CLIENT_IP=#{@env['HTTP_CLIENT_IP'].inspect} " + "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - client_ips = remove_proxies [client_ip, forwarded_ip, remote_addrs].flatten - if client_ips.present? - client_ips.first - else - # If there is no client ip we can return first valid proxy ip from REMOTE_ADDR - remote_addrs.find { |ip| valid_ip? ip } - end + # We assume these things about the IP headers: + # + # - X-Forwarded-For will be a list of IPs, one per proxy, or blank + # - Client-Ip is propagated from the outermost proxy, or is blank + # - REMOTE_ADDR will be the IP that made the request to Rack + ips = [forwarded_ips, client_ips, remote_addr].flatten.compact + + # If every single IP option is in the trusted list, just return REMOTE_ADDR + filter_proxies(ips).first || remote_addr end + # Memoizes the value returned by #calculate_ip and returns it for + # ActionDispatch::Request to use. def to_s @ip ||= calculate_ip end - private + protected def ips_from(header) - @env[header] ? @env[header].strip.split(/[,\s]+/) : [] - end - - def valid_ip?(ip) - ip =~ VALID_IP - end - - def not_a_proxy?(ip) - ip !~ @middleware.proxies + # Split the comma-separated list into an array of strings + ips = @env[header] ? @env[header].strip.split(/[,\s]+/) : [] + # Only return IPs that are valid according to the regex + ips.select{ |ip| ip =~ VALID_IP } end - def remove_proxies(ips) - ips.select { |ip| valid_ip?(ip) && not_a_proxy?(ip) } + def filter_proxies(ips) + ips.reject { |ip| ip =~ @proxies } end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index ce5f89ee5b..1e6ed624b0 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -21,15 +21,12 @@ module ActionDispatch # # Session options: # - # * <tt>:secret</tt>: An application-wide key string or block returning a - # string called per generated digest. The block is called with the - # CGI::Session instance as an argument. It's important that the secret - # is not vulnerable to a dictionary attack. Therefore, you should choose - # a secret consisting of random numbers and letters and more than 30 - # characters. + # * <tt>:secret</tt>: An application-wide key string. It's important that + # the secret is not vulnerable to a dictionary attack. Therefore, you + # should choose a secret consisting of random numbers and letters and + # more than 30 characters. # # secret: '449fe2e7daee471bffae2fd8dc02313d' - # secret: Proc.new { User.current_user.secret_key } # # * <tt>:digest</tt>: The message digest algorithm used to verify session # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL, @@ -39,21 +36,38 @@ module ActionDispatch # "rake secret" and set the key in config/initializers/secret_token.rb. # # Note that changing digest or secret invalidates all existing sessions! - class CookieStore < Rack::Session::Cookie + class CookieStore < Rack::Session::Abstract::ID include Compatibility include StaleSessionCheck include SessionObject - # Override rack's method + def initialize(app, options={}) + super(app, options.merge!(:cookie_only => true)) + end + def destroy_session(env, session_id, options) - new_sid = super + new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} new_sid end + def load_session(env) + stale_session_check! do + data = unpacked_cookie_data(env) + data = persistent_session_id!(data) + [data["session_id"], data] + end + end + private + def extract_session_id(env) + stale_session_check! do + unpacked_cookie_data(env)["session_id"] + end + end + def unpacked_cookie_data(env) env["action_dispatch.request.unsigned_session_cookie"] ||= begin stale_session_check! do @@ -65,6 +79,12 @@ module ActionDispatch end end + def persistent_session_id!(data, sid=nil) + data ||= {} + data["session_id"] ||= sid || generate_sid + data + end + def set_session(env, sid, session_data, options) session_data["session_id"] = sid session_data diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 2b37a8d026..fcc5bc12c4 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -16,9 +16,9 @@ module ActionDispatch # catches the exceptions and returns a FAILSAFE_RESPONSE. class ShowExceptions FAILSAFE_RESPONSE = [500, { 'Content-Type' => 'text/plain' }, - ["500 Internal Server Error\n" << - "If you are the administrator of this website, then please read this web " << - "application's log file and/or the web server's log file to find out what " << + ["500 Internal Server Error\n" \ + "If you are the administrator of this website, then please read this web " \ + "application's log file and/or the web server's log file to find out what " \ "went wrong."]] def initialize(app, exceptions_app) @@ -27,13 +27,10 @@ module ActionDispatch end def call(env) - begin - response = @app.call(env) - rescue Exception => exception - raise exception if env['action_dispatch.show_exceptions'] == false - end - - response || render_exception(env, exception) + @app.call(env) + rescue Exception => exception + raise exception if env['action_dispatch.show_exceptions'] == false + render_exception(env, exception) end private diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb index 9098f4e170..9e03cbf2b7 100644 --- a/actionpack/lib/action_dispatch/middleware/ssl.rb +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -45,7 +45,7 @@ module ActionDispatch # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02 def hsts_headers if @hsts - value = "max-age=#{@hsts[:expires]}" + value = "max-age=#{@hsts[:expires].to_i}" value += "; includeSubDomains" if @hsts[:subdomains] { 'Strict-Transport-Security' => value } else diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index e3b15b43b9..1c9512ad01 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -6,7 +6,7 @@ module ActionDispatch def initialize(root, cache_control) @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ - @file_server = ::Rack::File.new(@root, cache_control) + @file_server = ::Rack::File.new(@root, 'Cache-Control' => cache_control) end def match?(path) diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb index 823f5d25b6..ab24118f3e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb @@ -1,8 +1,8 @@ <% unless @exception.blamed_files.blank? %> <% if (hide = @exception.blamed_files.length > 8) %> - <a href="#" onclick="document.getElementById('blame_trace').style.display='block'; return false;">Show blamed files</a> + <a href="#" onclick="toggleTrace()">Toggle blamed files</a> <% end %> - <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%=h @exception.describe_blame %></code></pre> + <pre id="blame_trace" <%='style="display:none"' if hide %>><code><%= @exception.describe_blame %></code></pre> <% end %> <% @@ -18,14 +18,17 @@ %> <h2 style="margin-top: 30px">Request</h2> -<p><b>Parameters</b>: <pre><%=h request_dump %></pre></p> +<p><b>Parameters</b>:</p> <pre><%= request_dump %></pre> -<p><a href="#" onclick="document.getElementById('session_dump').style.display='block'; return false;">Show session dump</a></p> -<div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> - -<p><a href="#" onclick="document.getElementById('env_dump').style.display='block'; return false;">Show env dump</a></p> -<div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> +<div class="details"> + <div class="summary"><a href="#" onclick="toggleSessionDump()">Toggle session dump</a></div> + <div id="session_dump" style="display:none"><pre><%= debug_hash @request.session %></pre></div> +</div> +<div class="details"> + <div class="summary"><a href="#" onclick="toggleEnvDump()">Toggle env dump</a></div> + <div id="env_dump" style="display:none"><pre><%= debug_hash @request.env.slice(*@request.class::ENV_METHODS) %></pre></div> +</div> <h2 style="margin-top: 30px">Response</h2> -<p><b>Headers</b>: <pre><%=h defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre></p> +<p><b>Headers</b>:</p> <pre><%= defined?(@response) ? @response.headers.inspect.gsub(',', ",\n") : 'None' %></pre> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb new file mode 100644 index 0000000000..38429cb78e --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_source.erb @@ -0,0 +1,25 @@ +<% if @source_extract %> +<div class="source"> +<div class="info"> + Extracted source (around line <strong>#<%= @line_number %></strong>): +</div> +<div class="data"> + <table cellpadding="0" cellspacing="0" class="lines"> + <tr> + <td> + <pre class="line_numbers"> + <% @source_extract.keys.each do |line_number| %> +<span><%= line_number -%></span> + <% end %> + </pre> + </td> +<td width="100%"> +<pre> +<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @line_number -%>"><%= source -%></div><% end -%> +</pre> +</td> + </tr> + </table> +</div> +</div> +<% end %> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb index 8771b5fd6d..9d947aea40 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_trace.erb @@ -12,15 +12,15 @@ <div id="traces"> <% names.each do |name| %> <% - show = "document.getElementById('#{name.gsub(/\s/, '-')}').style.display='block';" - hide = (names - [name]).collect {|hide_name| "document.getElementById('#{hide_name.gsub(/\s/, '-')}').style.display='none';"} + show = "show('#{name.gsub(/\s/, '-')}');" + hide = (names - [name]).collect {|hide_name| "hide('#{hide_name.gsub(/\s/, '-')}');"} %> <a href="#" onclick="<%= hide.join %><%= show %>; return false;"><%= name %></a> <%= '|' unless names.last == name %> <% end %> <% traces.each do |name, trace| %> <div id="<%= name.gsub(/\s/, '-') %>" style="display: <%= (name == "Application Trace") ? 'block' : 'none' %>;"> - <pre><code><%=h trace.join "\n" %></code></pre> + <pre><code><%= trace.join "\n" %></code></pre> </div> <% end %> </div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb index c5043c5e7b..57a2940802 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb @@ -1,10 +1,16 @@ -<h1> - <%=h @exception.class.to_s %> - <% if @request.parameters['controller'] %> - in <%=h @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%=h @request.parameters['action'] %><% end %> - <% end %> -</h1> -<pre><%=h @exception.message %></pre> +<header> + <h1> + <%= @exception.class.to_s %> + <% if @request.parameters['controller'] %> + in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %> + <% end %> + </h1> +</header> -<%= render template: "rescues/_trace" %> -<%= render template: "rescues/_request_and_response" %> +<div id="container"> + <h2><%= @exception.message %></h2> + + <%= render template: "rescues/_source" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb index 1a308707d1..9878c2747e 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/layout.erb @@ -4,7 +4,11 @@ <meta charset="utf-8" /> <title>Action Controller: Exception caught</title> <style> - body { background-color: #fff; color: #333; } + body { + background-color: #FAFAFA; + color: #333; + margin: 0px; + } body, p, ol, ul, td { font-family: helvetica, verdana, arial, sans-serif; @@ -13,16 +17,127 @@ } pre { - background-color: #eee; - padding: 10px; font-size: 11px; white-space: pre-wrap; } - a { color: #000; } + pre.box { + border: 1px solid #EEE; + padding: 10px; + margin: 0px; + width: 958px; + } + + header { + color: #F0F0F0; + background: #C52F24; + padding: 0.5em 1.5em; + } + + h2 { + color: #C52F24; + line-height: 25px; + } + + .details { + border: 1px solid #D0D0D0; + border-radius: 4px; + margin: 1em 0px; + display: block; + width: 978px; + } + + .summary { + padding: 8px 15px; + border-bottom: 1px solid #D0D0D0; + display: block; + } + + .details pre { + margin: 5px; + border: none; + } + + #container { + box-sizing: border-box; + width: 100%; + padding: 0 1.5em; + } + + .source * { + margin: 0px; + padding: 0px; + } + + .source { + border: 1px solid #D9D9D9; + background: #ECECEC; + width: 978px; + } + + .source pre { + padding: 10px 0px; + border: none; + } + + .source .data { + font-size: 80%; + overflow: auto; + background-color: #FFF; + } + + .info { + padding: 0.5em; + } + + .source .data .line_numbers { + background-color: #ECECEC; + color: #AAA; + padding: 1em .5em; + border-right: 1px solid #DDD; + text-align: right; + } + + .line { + padding-left: 10px; + } + + .line:hover { + background-color: #F6F6F6; + } + + .line.active { + background-color: #FFCCCC; + } + + a { color: #980905; } a:visited { color: #666; } - a:hover { color: #fff; background-color:#000; } + a:hover { color: #C52F24; } + + <%= yield :style %> </style> + + <script> + var toggle = function(id) { + var s = document.getElementById(id).style; + s.display = s.display == 'none' ? 'block' : 'none'; + } + var show = function(id) { + document.getElementById(id).style.display = 'block'; + } + var hide = function(id) { + document.getElementById(id).style.display = 'none'; + } + var toggleTrace = function() { + toggle('blame_trace'); + } + var toggleSessionDump = function() { + toggle('session_dump'); + } + var toggleEnvDump = function() { + toggle('env_dump'); + } + </script> </head> <body> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb index dbfdf76947..ca14215946 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/missing_template.erb @@ -1,2 +1,7 @@ -<h1>Template is missing</h1> -<p><%=h @exception.message %></p> +<header> + <h1>Template is missing</h1> +</header> + +<div id="container"> + <h2><%= @exception.message %></h2> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb index 6c903d6a17..61690d3e50 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/routing_error.erb @@ -1,25 +1,30 @@ -<h1>Routing Error</h1> -<p><pre><%=h @exception.message %></pre></p> -<% unless @exception.failures.empty? %> - <p> - <h2>Failure reasons:</h2> - <ol> - <% @exception.failures.each do |route, reason| %> - <li><code><%=h route.inspect.gsub('\\', '') %></code> failed because <%=h reason.downcase %></li> - <% end %> - </ol> - </p> -<% end %> -<%= render template: "rescues/_trace" %> +<header> + <h1>Routing Error</h1> +</header> +<div id="container"> + <h2><%= @exception.message %></h2> + <% unless @exception.failures.empty? %> + <p> + <h2>Failure reasons:</h2> + <ol> + <% @exception.failures.each do |route, reason| %> + <li><code><%= route.inspect.gsub('\\', '') %></code> failed because <%= reason.downcase %></li> + <% end %> + </ol> + </p> + <% end %> -<h2> - Routes -</h2> + <%= render template: "rescues/_trace" %> -<p> - Routes match in priority from top to bottom -</p> + <% if @routes_inspector %> + <h2> + Routes + </h2> -<%= render layout: "routes/route_wrapper" do %> - <%= render partial: "routes/route", collection: @routes %> -<% end %> + <p> + Routes match in priority from top to bottom + </p> + + <%= @routes_inspector.format(ActionDispatch::Routing::HtmlTableFormatter.new(self)) %> + <% end %> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb index a1b377f68c..63216ef7c5 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/template_error.erb @@ -1,17 +1,43 @@ -<h1> - <%=h @exception.original_exception.class.to_s %> in - <%=h @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%=h @request.parameters["action"] %> -</h1> +<% @source_extract = @exception.source_extract(0, :html) %> +<header> + <h1> + <%= @exception.original_exception.class.to_s %> in + <%= @request.parameters["controller"].capitalize if @request.parameters["controller"]%>#<%= @request.parameters["action"] %> + </h1> +</header> -<p> - Showing <i><%=h @exception.file_name %></i> where line <b>#<%=h @exception.line_number %></b> raised: - <pre><code><%=h @exception.message %></code></pre> -</p> +<div id="container"> + <p> + Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised: + </p> + <pre><code><%= @exception.message %></code></pre> -<p>Extracted source (around line <b>#<%=h @exception.line_number %></b>): -<pre><code><%=h @exception.source_extract %></code></pre></p> + <div class="source"> + <div class="info"> + <p>Extracted source (around line <strong>#<%= @exception.line_number %></strong>):</p> + </div> + <div class="data"> + <table cellpadding="0" cellspacing="0" class="lines"> + <tr> + <td> + <pre class="line_numbers"> + <% @source_extract.keys.each do |line_number| %> +<span><%= line_number -%></span> + <% end %> + </pre> + </td> +<td width="100%"> +<pre> +<% @source_extract.each do |line, source| -%><div class="line<%= " active" if line == @exception.line_number -%>"><%= source -%></div><% end -%> +</pre> +</td> + </tr> + </table> +</div> +</div> -<p><%=h @exception.sub_template_message %></p> + <p><%= @exception.sub_template_message %></p> -<%= render template: "rescues/_trace" %> -<%= render template: "rescues/_request_and_response" %> + <%= render template: "rescues/_trace" %> + <%= render template: "rescues/_request_and_response" %> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb index 683379da10..c1fbf67eed 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb @@ -1,2 +1,6 @@ -<h1>Unknown action</h1> -<p><%=h @exception.message %></p> +<header> + <h1>Unknown action</h1> +</header> +<div id="container"> + <h2><%= @exception.message %></h2> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb index dc17cb77ef..9026c4eeb2 100644 --- a/actionpack/lib/action_dispatch/middleware/templates/routes/_route_wrapper.html.erb +++ b/actionpack/lib/action_dispatch/middleware/templates/routes/_table.html.erb @@ -1,7 +1,7 @@ -<style type='text/css'> - #route_table td {padding: 0 30px;} - #route_table {margin: 0 auto 0;} -</style> +<% content_for :style do %> + #route_table td { padding: 0 30px; } + #route_table { margin: 0 auto 0; } +<% end %> <table id='route_table' class='route_table'> <thead> diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index 8d7461ecc3..ea3e8357d4 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -61,32 +61,46 @@ module ActionDispatch ## # This class is just used for displaying route information when someone - # executes `rake routes`. People should not use this class. + # executes `rake routes` or looks at the RoutingError page. + # People should not use this class. class RoutesInspector # :nodoc: - def initialize - @engines = Hash.new + def initialize(routes) + @engines = {} + @routes = routes end - def format(all_routes, filter = nil) - if filter - all_routes = all_routes.select{ |route| route.defaults[:controller] == filter } + def format(formatter, filter = nil) + routes_to_display = filter_routes(filter) + + routes = collect_routes(routes_to_display) + formatter.section :application, 'Application routes', routes + + @engines.each do |name, engine_routes| + formatter.section :engine, "Routes for #{name}", engine_routes end - routes = collect_routes(all_routes) + formatter.result + end + + private - formatted_routes(routes) + - formatted_routes_for_engines + def filter_routes(filter) + if filter + @routes.select { |route| route.defaults[:controller] == filter } + else + @routes + end end def collect_routes(routes) - routes = routes.collect do |route| + routes.collect do |route| RouteWrapper.new(route) end.reject do |route| route.internal? end.collect do |route| collect_engine_routes(route) - {:name => route.name, :verb => route.verb, :path => route.path, :reqs => route.reqs } + { name: route.name, verb: route.verb, path: route.path, reqs: route.reqs } end end @@ -100,21 +114,49 @@ module ActionDispatch @engines[name] = collect_routes(routes.routes) end end + end - def formatted_routes_for_engines - @engines.map do |name, routes| - ["\nRoutes for #{name}:"] + formatted_routes(routes) - end.flatten + class ConsoleFormatter + def initialize + @buffer = [] end - def formatted_routes(routes) - name_width = routes.map{ |r| r[:name].length }.max - verb_width = routes.map{ |r| r[:verb].length }.max - path_width = routes.map{ |r| r[:path].length }.max + def result + @buffer.join("\n") + end - routes.map do |r| - "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + def section(type, title, routes) + @buffer << "\n#{title}:" unless type == :application + @buffer << draw_section(routes) + end + + private + def draw_section(routes) + name_width = routes.map { |r| r[:name].length }.max + verb_width = routes.map { |r| r[:verb].length }.max + path_width = routes.map { |r| r[:path].length }.max + + routes.map do |r| + "#{r[:name].rjust(name_width)} #{r[:verb].ljust(verb_width)} #{r[:path].ljust(path_width)} #{r[:reqs]}" + end end + end + + class HtmlTableFormatter + def initialize(view) + @view = view + @buffer = [] + end + + def section(type, title, routes) + @buffer << %(<tr><th colspan="4">#{title}</th></tr>) + @buffer << @view.render(partial: "routes/route", collection: routes) + end + + def result + @view.raw @view.render(layout: "routes/table") { + @view.raw @buffer.join("\n") + } end end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 3c99932e72..a21383e091 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -26,15 +26,10 @@ module ActionDispatch def matches?(env) req = @request.new(env) - @constraints.each { |constraint| - if constraint.respond_to?(:matches?) && !constraint.matches?(req) - return false - elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req)) - return false - end - } - - return true + @constraints.none? do |constraint| + (constraint.respond_to?(:matches?) && !constraint.matches?(req)) || + (constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))) + end ensure req.reset_parameters end @@ -315,7 +310,7 @@ module ActionDispatch # A pattern can also point to a +Rack+ endpoint i.e. anything that # responds to +call+: # - # match 'photos/:id', to: lambda {|hash| [200, {}, "Coming soon"] } + # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] } # match 'photos/:id', to: PhotoRackApp # # Yes, controller actions are just rack endpoints # match 'photos/:id', to: PhotosController.action(:show) @@ -360,7 +355,7 @@ module ActionDispatch # +call+ or a string representing a controller's action. # # match 'path', to: 'controller#action' - # match 'path', to: lambda { |env| [200, {}, "Success!"] } + # match 'path', to: lambda { |env| [200, {}, ["Success!"]] } # match 'path', to: RackApp # # [:on] diff --git a/actionpack/lib/action_dispatch/testing/assertions/dom.rb b/actionpack/lib/action_dispatch/testing/assertions/dom.rb index 6c61d4e61a..8f90a1223e 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/dom.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/dom.rb @@ -20,7 +20,7 @@ module ActionDispatch def assert_dom_not_equal(expected, actual, message = "") expected_dom = HTML::Document.new(expected).root actual_dom = HTML::Document.new(actual).root - refute_equal expected_dom, actual_dom + assert_not_equal expected_dom, actual_dom end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 79dff7d121..9210bffd1d 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -1,5 +1,6 @@ require 'uri' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/string/access' require 'action_controller/metal/exceptions' module ActionDispatch @@ -208,11 +209,9 @@ module ActionDispatch end def fail_on(exception_class) - begin - yield - rescue exception_class => e - raise MiniTest::Assertion, e.message - end + yield + rescue exception_class => e + raise MiniTest::Assertion, e.message end end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 95cd89a166..1fc5933e98 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -491,9 +491,6 @@ module ActionDispatch include ActionController::TemplateAssertions include ActionDispatch::Routing::UrlFor - # Use AD::IntegrationTest for acceptance tests - register_spec_type(/(Acceptance|Integration) ?Test\z/i, self) - @@app = nil def self.app diff --git a/actionpack/lib/action_dispatch/testing/performance_test.rb b/actionpack/lib/action_dispatch/testing/performance_test.rb deleted file mode 100644 index 13fe693c32..0000000000 --- a/actionpack/lib/action_dispatch/testing/performance_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'active_support/testing/performance' - -module ActionDispatch - # An integration test that runs a code profiler on your test methods. - # Profiling output for combinations of each test method, measurement, and - # output format are written to your tmp/performance directory. - class PerformanceTest < ActionDispatch::IntegrationTest - include ActiveSupport::Testing::Performance - end -end diff --git a/actionpack/lib/action_pack.rb b/actionpack/lib/action_pack.rb index 39c7faf740..ad5acd8080 100644 --- a/actionpack/lib/action_pack.rb +++ b/actionpack/lib/action_pack.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2012 David Heinemeier Hansson +# Copyright (c) 2004-2013 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 8bbf52382a..4aafbcb655 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2012 David Heinemeier Hansson +# Copyright (c) 2004-2013 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 668515df59..08253de3f4 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -6,7 +6,7 @@ require 'action_view/log_subscriber' module ActionView #:nodoc: # = Action View Base # - # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERb + # Action View templates can be written in several ways. If the template file has a <tt>.erb</tt> extension then it uses a mixture of ERB # (included in Ruby) and HTML. If the template file has a <tt>.builder</tt> extension then Jim Weirich's Builder::XmlMarkup library is used. # # == ERB diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb index f3f6b425a8..4507861dcc 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionpack/lib/action_view/digestor.rb @@ -24,16 +24,17 @@ module ActionView @@cache = ThreadSafe::Cache.new def self.digest(name, format, finder, options = {}) - @@cache["#{name}.#{format}"] ||= begin + cache_key = [name, format] + Array.wrap(options[:dependencies]) + @@cache[cache_key.join('.')] ||= begin klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - klass.new(name, format, finder).digest + klass.new(name, format, finder, options).digest end end - attr_reader :name, :format, :finder + attr_reader :name, :format, :finder, :options - def initialize(name, format, finder) - @name, @format, @finder = name, format, finder + def initialize(name, format, finder, options={}) + @name, @format, @finder, @options = name, format, finder, options end def digest @@ -81,9 +82,11 @@ module ActionView end def dependency_digest - dependencies.collect do |template_name| + template_digests = dependencies.collect do |template_name| Digestor.digest(template_name, format, finder, partial: true) - end.join("-") + end + + (template_digests + injected_dependencies).join("-") end def render_dependencies @@ -105,6 +108,10 @@ module ActionView def explicit_dependencies source.scan(EXPLICIT_DEPENDENCY).flatten.uniq end + + def injected_dependencies + Array.wrap(options[:dependencies]) + end end class PartialDigestor < Digestor # :nodoc: diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 995aa10afb..8fc78ea7fb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -167,7 +167,7 @@ module ActionView if @virtual_path [ *Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name), - Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context) + Digestor.digest(@virtual_path, formats.last.to_sym, lookup_context, dependencies: view_cache_dependencies) ] else name diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index d361a69a92..34fc23ac1a 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -27,14 +27,12 @@ module ActionView # new_record: true # </pre> def debug(object) - begin - Marshal::dump(object) - object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe - content_tag(:pre, object, :class => "debug_dump") - rescue Exception # errors from Marshal or YAML - # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback - content_tag(:code, object.to_yaml, :class => "debug_dump") - end + Marshal::dump(object) + object = ERB::Util.html_escape(object.to_yaml).gsub(" ", " ").html_safe + content_tag(:pre, object, :class => "debug_dump") + rescue Exception # errors from Marshal or YAML + # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback + content_tag(:code, object.to_yaml, :class => "debug_dump") end end end diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 7e4f5fa65c..4a31de715d 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -84,7 +84,7 @@ module ActionView # # <form action="/people/256" class="edit_person" id="edit_person_256" method="post"> # <div style="margin:0;padding:0;display:inline"> - # <input name="_method" type="hidden" value="put" /> + # <input name="_method" type="hidden" value="patch" /> # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: @@ -101,9 +101,9 @@ module ActionView # and generate HTML accordingly. # # The controller would receive the form data again in <tt>params[:person]</tt>, ready to be - # passed to <tt>Person#update_attributes</tt>: + # passed to <tt>Person#update</tt>: # - # if @person.update_attributes(params[:person]) + # if @person.update(params[:person]) # # success # else # # error handling @@ -242,7 +242,7 @@ module ActionView # # is then equivalent to something like: # - # <%= form_for @post, as: :post, url: post_path(@post), method: :put, html: { class: "edit_post", id: "edit_post_45" } do |f| %> + # <%= form_for @post, as: :post, url: post_path(@post), method: :patch, html: { class: "edit_post", id: "edit_post_45" } do |f| %> # ... # <% end %> # @@ -318,7 +318,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-remote='true'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> @@ -336,7 +336,7 @@ module ActionView # # <form action='http://www.example.com' method='post' data-behavior='autosave' name='go'> # <div style='margin:0;padding:0;display:inline'> - # <input name='_method' type='hidden' value='put' /> + # <input name='_method' type='hidden' value='patch' /> # </div> # ... # </form> @@ -388,9 +388,9 @@ module ActionView # 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) + # def labelled_form_for(record_or_name_or_array, *args, &block) # options = args.extract_options! - # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &proc) + # form_for(record_or_name_or_array, *(args << options.merge(builder: LabellingFormBuilder)), &block) # end # # If you don't need to attach a form to a model instance, then check out @@ -412,10 +412,9 @@ module ActionView # <%= form_for @invoice, url: external_url, authenticity_token: false do |f| # ... # <% end %> - def form_for(record, options = {}, &proc) + def form_for(record, options = {}, &block) raise ArgumentError, "Missing block" unless block_given? - - options[:html] ||= {} + html_options = options[:html] ||= {} case record when String, Symbol @@ -428,17 +427,16 @@ module ActionView apply_form_for_options!(record, object, options) end - options[:html][:data] = options.delete(:data) if options.has_key?(:data) - options[:html][:remote] = options.delete(:remote) if options.has_key?(:remote) - options[:html][:method] = options.delete(:method) if options.has_key?(:method) - options[:html][:authenticity_token] = options.delete(:authenticity_token) + html_options[:data] = options.delete(:data) if options.has_key?(:data) + html_options[:remote] = options.delete(:remote) if options.has_key?(:remote) + html_options[:method] = options.delete(:method) if options.has_key?(:method) + html_options[:authenticity_token] = options.delete(:authenticity_token) - builder = options[:parent_builder] = instantiate_builder(object_name, object, options) - fields_for = fields_for(object_name, object, options, &proc) - default_options = builder.multipart? ? { multipart: true } : {} - default_options.merge!(options.delete(:html)) + builder = instantiate_builder(object_name, object, options) + output = capture(builder, &block) + html_options[:multipart] = builder.multipart? - form_tag(options.delete(:url) || {}, default_options) { fields_for } + form_tag(options[:url] || {}, html_options) { output } end def apply_form_for_options!(record, object, options) #:nodoc: @@ -707,9 +705,7 @@ module ActionView # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, options = {}, &block) builder = instantiate_builder(record_name, record_object, options) - output = capture(builder, &block) - output.concat builder.hidden_field(:id) if output && options[:hidden_field_id] && !builder.emitted_hidden_id? - output + capture(builder, &block) end # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object @@ -897,7 +893,7 @@ module ActionView # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # - # @invoice.update_attributes(params[:invoice]) + # @invoice.update(params[:invoice]) # # wouldn't update the flag. # @@ -1174,12 +1170,15 @@ module ActionView attr_accessor :object_name, :object, :options - attr_reader :multipart, :parent_builder, :index + attr_reader :multipart, :index alias :multipart? :multipart def multipart=(multipart) @multipart = multipart - parent_builder.multipart = multipart if parent_builder + + if parent_builder = @options[:parent_builder] + parent_builder.multipart = multipart + end end def self._to_partial_path @@ -1201,7 +1200,6 @@ module ActionView @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options - @parent_builder = options[:parent_builder] @default_options = @options ? @options.slice(:index, :namespace) : {} if @object_name.to_s.match(/\[\]$/) if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) @@ -1478,8 +1476,8 @@ module ActionView def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] - fields_options[:parent_builder] = self fields_options[:namespace] = options[:namespace] + fields_options[:parent_builder] = self case record_name when String, Symbol @@ -1569,7 +1567,7 @@ module ActionView # invoice the user unchecks its check box, no +paid+ parameter is sent. So, # any mass-assignment idiom like # - # @invoice.update_attributes(params[:invoice]) + # @invoice.update(params[:invoice]) # # wouldn't update the flag. # @@ -1820,13 +1818,17 @@ module ActionView end end - def fields_for_nested_model(name, object, options, block) + def fields_for_nested_model(name, object, fields_options, block) object = convert_to_model(object) + emit_hidden_id = object.persisted? && fields_options.fetch(:include_id) { + options.fetch(:include_id, true) + } - parent_include_id = self.options.fetch(:include_id, true) - include_id = options.fetch(:include_id, parent_include_id) - options[:hidden_field_id] = object.persisted? && include_id - @template.fields_for(name, object, options, &block) + @template.fields_for(name, object, fields_options) do |f| + output = @template.capture(f, &block) + output.concat f.hidden_field(:id) if output && emit_hidden_id && !f.emitted_hidden_id? + output + end end def nested_child_index(name) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 1b5b788a35..bcad05e033 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -130,7 +130,7 @@ module ActionView # the user deselects all roles from +role_ids+ multiple select box, no +role_ids+ parameter is sent. So, # any mass-assignment idiom like # - # @user.update_attributes(params[:user]) + # @user.update(params[:user]) # # wouldn't update roles. # diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index ff83ef3ca1..86d9f94067 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -26,7 +26,7 @@ module ActionView # ==== Options # * <tt>:multipart</tt> - If set to true, the enctype is set to "multipart/form-data". # * <tt>:method</tt> - The method to use when submitting the form, usually either "get" or "post". - # If "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> + # If "patch", "put", "delete", or another verb is used, a hidden input with name <tt>_method</tt> # is added to simulate the verb over post. # * <tt>:authenticity_token</tt> - Authenticity token to use in the form. Use only if you need to # pass custom authenticity token string, or to not add authenticity_token field at all @@ -184,7 +184,7 @@ module ActionView # # => <label for="name">Name</label> # # label_tag 'name', 'Your name' - # # => <label for="name">Your Name</label> + # # => <label for="name">Your name</label> # # label_tag 'name', nil, class: 'small_label' # # => <label for="name" class="small_label">Name</label> @@ -526,19 +526,19 @@ module ActionView # # ==== Examples # image_submit_tag("login.png") - # # => <input src="/images/login.png" type="image" /> + # # => <input alt="Login" src="/images/login.png" type="image" /> # # image_submit_tag("purchase.png", disabled: true) - # # => <input disabled="disabled" src="/images/purchase.png" type="image" /> + # # => <input alt="Purchase" disabled="disabled" src="/images/purchase.png" type="image" /> # - # image_submit_tag("search.png", class: 'search_button') - # # => <input class="search_button" src="/images/search.png" type="image" /> + # image_submit_tag("search.png", class: 'search_button', alt: 'Find') + # # => <input alt="Find" class="search_button" src="/images/search.png" type="image" /> # # image_submit_tag("agree.png", disabled: true, class: "agree_disagree_button") - # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> + # # => <input alt="Agree" class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> # # image_submit_tag("save.png", data: { confirm: "Are you sure?" }) - # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" /> + # # => <input alt="Save" src="/images/save.png" data-confirm="Are you sure?" type="image" /> def image_submit_tag(source, options = {}) options = options.stringify_keys @@ -550,7 +550,7 @@ module ActionView options["data-confirm"] = confirm end - tag :input, { "type" => "image", "src" => path_to_image(source) }.update(options) + tag :input, { "alt" => image_alt(source), "type" => "image", "src" => path_to_image(source) }.update(options) end # Creates a field set for grouping HTML form elements. diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 33194250b7..271a194913 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -65,8 +65,8 @@ module ActionView # # produces: # - # <tr id="person_123" class="person">...</tr> - # <tr id="person_124" class="person">...</tr> + # <tr id="person_123" class="person">...</tr> + # <tr id="person_124" class="person">...</tr> # # content_tag_for also accepts a hash of options, which will be converted to # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined @@ -95,7 +95,11 @@ module ActionView options[:class] = "#{dom_class(record, prefix)} #{options[:class]}".rstrip options[:id] = dom_id(record, prefix) - content_tag(tag_name, capture(record, &block), options) + if block_given? + content_tag(tag_name, capture(record, &block), options) + else + content_tag(tag_name, "", options) + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb index 45f0bc3d7b..d27df45b5a 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -21,7 +21,7 @@ module ActionView if block_given? yield builder else - builder.check_box + builder.label + render_component(builder) end end @@ -31,6 +31,12 @@ module ActionView rendered_collection + hidden end + + private + + def render_component(builder) + builder.check_box + builder.label + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb index 4e33e79a36..e92a318c73 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_helpers.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb @@ -44,7 +44,8 @@ module ActionView html_options = @html_options.dup [:checked, :selected, :disabled].each do |option| - next unless current_value = @options[option] + current_value = @options[option] + next if current_value.nil? accept = if current_value.respond_to?(:call) current_value.call(item) diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb index ba2035f074..81f2ecb2b3 100644 --- a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb +++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -20,10 +20,16 @@ module ActionView if block_given? yield builder else - builder.radio_button + builder.label + render_component(builder) end end end + + private + + def render_component(builder) + builder.radio_button + builder.label + end end end end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 26d2142df9..2e124cf085 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -112,12 +112,12 @@ module ActionView # highlight('You searched for: rails', 'rails', highlighter: '<a href="search?q=\1">\1</a>') # # => You searched for: <a href="search?q=rails">rails</a> def highlight(text, phrases, options = {}) - highlighter = options.fetch(:highlighter, '<mark>\1</mark>') - text = sanitize(text) if options.fetch(:sanitize, true) + if text.blank? || phrases.blank? text else + highlighter = options.fetch(:highlighter, '<mark>\1</mark>') match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') text.gsub(/(#{match})(?![^<]*?>)/i, highlighter) end.html_safe diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index aeee662071..bade121d44 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -170,7 +170,7 @@ module ActionView # You can also use custom data attributes using the <tt>:data</tt> option: # # link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" } - # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?"">Visit Other Site</a> + # # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a> def link_to(name = nil, options = nil, html_options = nil, &block) html_options, options = options, name if block_given? options ||= {} diff --git a/actionpack/lib/action_view/record_identifier.rb b/actionpack/lib/action_view/record_identifier.rb index 2953654972..63f645431a 100644 --- a/actionpack/lib/action_view/record_identifier.rb +++ b/actionpack/lib/action_view/record_identifier.rb @@ -17,7 +17,7 @@ module ActionView # # controller # def update # post = Post.find(params[:id]) - # post.update_attributes(params[:post]) + # post.update(params[:post]) # # redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post) # end diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 37f93a13fc..43a88b0623 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -452,7 +452,7 @@ module ActionView def retrieve_template_keys keys = @locals.keys - keys << @variable + keys << @variable if @object || @collection keys << @variable_counter if @collection keys end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index aefc42be48..b927c69260 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/deprecation' require 'thread' module ActionView diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index e00056781d..b479f991bc 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -78,7 +78,7 @@ module ActionView end end - def source_extract(indentation = 0) + def source_extract(indentation = 0, output = :console) return unless num = line_number num = num.to_i @@ -88,13 +88,9 @@ module ActionView end_on_line = [ num + SOURCE_CODE_RADIUS - 1, source_code.length].min indent = end_on_line.to_s.size + indentation - line_counter = start_on_line return unless source_code = source_code[start_on_line..end_on_line] - source_code.sum do |line| - line_counter += 1 - "%#{indent}s: %s\n" % [line_counter, line] - end + formatted_code_for(source_code, start_on_line, indent, output) end def sub_template_of(template_path) @@ -123,6 +119,18 @@ module ActionView 'in ' end + file_name end + + def formatted_code_for(source_code, line_counter, indent, output) + start_value = (output == :html) ? {} : "" + source_code.inject(start_value) do |result, line| + line_counter += 1 + if output == :html + result.update(line_counter.to_s => "%#{indent}s %s\n" % ["", line]) + else + result << "%#{indent}s: %s\n" % [line_counter, line] + end + end + end end end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 8b23029bbc..1a1083bf00 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -118,7 +118,7 @@ module ActionView private - delegate :caching?, :to => "self.class" + delegate :caching?, to: :class # This is what child classes implement. No defaults are needed # because Resolver guarantees that the arguments are present and diff --git a/actionpack/lib/action_view/template/types.rb b/actionpack/lib/action_view/template/types.rb index 7611c9e708..db77cb5d19 100644 --- a/actionpack/lib/action_view/template/types.rb +++ b/actionpack/lib/action_view/template/types.rb @@ -1,6 +1,5 @@ require 'set' require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/object/blank' module ActionView class Template diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index a548b44780..4479da5bc4 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -30,9 +30,6 @@ module ActionView end end - # Use AV::TestCase for the base class for helpers and views - register_spec_type(/(Helper|View)( ?Test)?\z/i, self) - module Behavior extend ActiveSupport::Concern diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index 62f82a4c7a..eb9143c8f6 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -157,13 +157,11 @@ module AbstractController private def self.layout(formats) + find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) + rescue ActionView::MissingTemplate begin - find_template(name.underscore, {:formats => formats}, :_prefixes => ["layouts"]) + find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) rescue ActionView::MissingTemplate - begin - find_template("application", {:formats => formats}, :_prefixes => ["layouts"]) - rescue ActionView::MissingTemplate - end end end diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index e79008fa9d..7960e5b55b 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -69,12 +69,10 @@ module AbstractController end def test_declare_missing_helper - begin - AbstractHelpers.helper :missing - flunk "should have raised an exception" - rescue LoadError => e - assert_equal "helpers/missing_helper.rb", e.path - end + AbstractHelpers.helper :missing + flunk "should have raised an exception" + rescue LoadError => e + assert_equal "helpers/missing_helper.rb", e.path end def test_helpers_with_module_through_block diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 95bff0a204..bbcd289886 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -15,7 +15,7 @@ silence_warnings do Encoding.default_external = "UTF-8" end -require 'minitest/autorun' +require 'active_support/testing/autorun' require 'abstract_controller' require 'action_controller' require 'action_view' diff --git a/actionpack/test/controller/action_pack_assertions_test.rb b/actionpack/test/controller/action_pack_assertions_test.rb index b94f45bfe7..5d727b3811 100644 --- a/actionpack/test/controller/action_pack_assertions_test.rb +++ b/actionpack/test/controller/action_pack_assertions_test.rb @@ -259,7 +259,7 @@ class ActionPackAssertionsControllerTest < ActionController::TestCase def test_flash_exist process :flash_me assert flash.any? - assert_present flash['hello'] + assert flash['hello'].present? end def test_flash_does_not_exist diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index 2428cd7433..ca86837a2c 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -296,6 +296,24 @@ class CacheHelperOutputBufferTest < ActionController::TestCase end end +class ViewCacheDependencyTest < ActionController::TestCase + class NoDependenciesController < ActionController::Base + end + + class HasDependenciesController < ActionController::Base + view_cache_dependency { "trombone" } + view_cache_dependency { "flute" } + end + + def test_view_cache_dependencies_are_empty_by_default + assert NoDependenciesController.new.view_cache_dependencies.empty? + end + + def test_view_cache_dependencies_are_listed_in_declaration_order + assert_equal %w(trombone flute), HasDependenciesController.new.view_cache_dependencies + end +end + class DeprecatedPageCacheExtensionTest < ActiveSupport::TestCase def test_page_cache_extension_binds_default_static_extension deprecation_behavior = ActiveSupport::Deprecation.behavior diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 1c59dd5953..3b79161ad3 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -450,11 +450,9 @@ class FilterTest < ActionController::TestCase class RescuingAroundFilterWithBlock def around(controller) - begin - yield - rescue ErrorToRescue => ex - controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" - end + yield + rescue ErrorToRescue => ex + controller.__send__ :render, :text => "I rescued this: #{ex.inspect}" end end diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index faf923e929..ebf6d224aa 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -111,21 +111,21 @@ class HttpTokenAuthenticationTest < ActionController::TestCase assert_equal(expected, actual) end - test "token_and_options returns correct token" do + test "token_and_options returns correct token with value after the equal sign" do token = 'rcHu+=HzSFw89Ypyhn/896A==f34' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end - test "token_and_options returns correct token" do + test "token_and_options returns correct token with slashes" do token = 'rcHu+\\\\"/896A' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token assert_equal(expected, actual) end - test "token_and_options returns correct token" do + test "token_and_options returns correct token with quotes" do token = '\"quote\" pretty' actual = ActionController::HttpAuthentication::Token.token_and_options(sample_request(token)).first expected = token diff --git a/actionpack/test/controller/live_stream_test.rb b/actionpack/test/controller/live_stream_test.rb index 20e433d1ec..3b1a07d7af 100644 --- a/actionpack/test/controller/live_stream_test.rb +++ b/actionpack/test/controller/live_stream_test.rb @@ -40,7 +40,7 @@ module ActionController def thread_locals tc.assert_equal 'aaron', Thread.current[:setting] - tc.refute_equal Thread.current.object_id, Thread.current[:originating_thread] + tc.assert_not_equal Thread.current.object_id, Thread.current[:originating_thread] response.headers['Content-Type'] = 'text/event-stream' %w{ hello world }.each do |word| diff --git a/actionpack/test/controller/output_escaping_test.rb b/actionpack/test/controller/output_escaping_test.rb index f6913a2138..43a8c05cda 100644 --- a/actionpack/test/controller/output_escaping_test.rb +++ b/actionpack/test/controller/output_escaping_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' class OutputEscapingTest < ActiveSupport::TestCase test "escape_html shouldn't die when passed nil" do - assert_blank ERB::Util.h(nil) + assert ERB::Util.h(nil).blank? end test "escapeHTML should escape strings" do diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 7640bc12a2..0e5bad7482 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -531,6 +531,10 @@ class TestController < ActionController::Base head :created, :content_type => "application/json" end + def head_ok_with_image_png_content_type + head :ok, :content_type => "image/png" + end + def head_with_location_header head :location => "/foo" end @@ -789,15 +793,13 @@ class RenderTest < ActionController::TestCase end def test_line_offset - begin - get :render_line_offset - flunk "the action should have raised an exception" - rescue StandardError => exc - line = exc.backtrace.first - assert(line =~ %r{:(\d+):}) - assert_equal "1", $1, - "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" - end + get :render_line_offset + flunk "the action should have raised an exception" + rescue StandardError => exc + line = exc.backtrace.first + assert(line =~ %r{:(\d+):}) + assert_equal "1", $1, + "The line offset is wrong, perhaps the wrong exception has been raised, exception was: #{exc.inspect}" end # :ported: compatibility @@ -1217,20 +1219,27 @@ class RenderTest < ActionController::TestCase def test_head_created post :head_created - assert_blank @response.body + assert @response.body.blank? assert_response :created end def test_head_created_with_application_json_content_type post :head_created_with_application_json_content_type - assert_blank @response.body - assert_equal "application/json", @response.content_type + assert @response.body.blank? + assert_equal "application/json", @response.header["Content-Type"] assert_response :created end + def test_head_ok_with_image_png_content_type + post :head_ok_with_image_png_content_type + assert @response.body.blank? + assert_equal "image/png", @response.header["Content-Type"] + assert_response :ok + end + def test_head_with_location_header get :head_with_location_header - assert_blank @response.body + assert @response.body.blank? assert_equal "/foo", @response.headers["Location"] assert_response :ok end @@ -1243,7 +1252,7 @@ class RenderTest < ActionController::TestCase end get :head_with_location_object - assert_blank @response.body + assert @response.body.blank? assert_equal "http://www.nextangle.com/customers/1", @response.headers["Location"] assert_response :ok end @@ -1251,14 +1260,14 @@ class RenderTest < ActionController::TestCase def test_head_with_custom_header get :head_with_custom_header - assert_blank @response.body + assert @response.body.blank? assert_equal "something", @response.headers["X-Custom-Header"] assert_response :ok end def test_head_with_www_authenticate_header get :head_with_www_authenticate_header - assert_blank @response.body + assert @response.body.blank? assert_equal "something", @response.headers["WWW-Authenticate"] assert_response :ok end @@ -1433,10 +1442,11 @@ class RenderTest < ActionController::TestCase end def test_locals_option_to_assert_template_is_not_supported + get :partial_collection_with_locals + warning_buffer = StringIO.new $stderr = warning_buffer - get :partial_collection_with_locals assert_template partial: 'customer_greeting', locals: { greeting: 'Bonjour' } assert_equal "the :locals option to #assert_template is only supported in a ActionView::TestCase\n", warning_buffer.string ensure @@ -1591,7 +1601,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = @last_modified get :conditional_hello assert_equal 304, @response.status.to_i - assert_blank @response.body + assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1606,7 +1616,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello assert_equal 200, @response.status.to_i - assert_present @response.body + assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1620,7 +1630,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = @last_modified get :conditional_hello_with_record assert_equal 304, @response.status.to_i - assert_blank @response.body + assert @response.body.blank? assert_equal @last_modified, @response.headers['Last-Modified'] end @@ -1635,7 +1645,7 @@ class LastModifiedRenderTest < ActionController::TestCase @request.if_modified_since = 'Thu, 16 Jul 2008 00:00:00 GMT' get :conditional_hello_with_record assert_equal 200, @response.status.to_i - assert_present @response.body + assert @response.body.present? assert_equal @last_modified, @response.headers['Last-Modified'] end diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index 1f637eb791..523a8d0572 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -320,7 +320,7 @@ class FreeCookieControllerTest < ActionController::TestCase test 'should not emit a csrf-token meta tag' do get :meta - assert_blank @response.body + assert @response.body.blank? end end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index f0430e516f..5e821046db 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -57,13 +57,13 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase end class MockController - def self.build(helpers) + def self.build(helpers, additional_options = {}) Class.new do - def url_options - options = super + define_method :url_options do + options = super() options[:protocol] ||= "http" options[:host] ||= "test.host" - options + options.merge(additional_options) end include helpers @@ -428,8 +428,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase routes.send(:pages_url) end - def setup_for_named_route - MockController.build(rs.url_helpers).new + def setup_for_named_route(options = {}) + MockController.build(rs.url_helpers, options).new end def test_named_route_without_hash @@ -456,6 +456,16 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_with_trailing_slash + rs.draw do + root "hello#index" + end + + routes = setup_for_named_route(trailing_slash: true) + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("http://test.host/?foo=bar", routes.send(:root_url, foo: :bar)) + end + def test_named_route_with_regexps rs.draw do get 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', diff --git a/actionpack/test/controller/spec_style_test.rb b/actionpack/test/controller/spec_style_test.rb deleted file mode 100644 index e118c584ca..0000000000 --- a/actionpack/test/controller/spec_style_test.rb +++ /dev/null @@ -1,208 +0,0 @@ -require "abstract_unit" - -class ApplicationController < ActionController::Base; end -class ModelsController < ApplicationController; end -module Admin - class WidgetsController < ApplicationController; end -end - -# ApplicationController -describe ApplicationController do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -describe ApplicationController, :index do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -describe ApplicationController, "unauthenticated user" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -describe "ApplicationControllerTest" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -describe "ApplicationControllerTest", :index do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -describe "ApplicationControllerTest", "unauthenticated user" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ApplicationController, @controller - end - end - end -end - -# ModelsController -describe ModelsController do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -describe ModelsController, :index do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -describe ModelsController, "unauthenticated user" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -describe "ModelsControllerTest" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -describe "ModelsControllerTest", :index do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -describe "ModelsControllerTest", "unauthenticated user" do - describe "nested" do - describe "even deeper" do - it "exists" do - assert_kind_of ModelsController, @controller - end - end - end -end - -# Nested Admin::WidgetsControllerTest -module Admin - class WidgetsControllerTest < ActionController::TestCase - test "exists" do - assert_kind_of Admin::WidgetsController, @controller - end - end - - describe WidgetsController do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end - end - - describe WidgetsController, "unauthenticated users" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end - end -end - -class Admin::WidgetsControllerTest < ActionController::TestCase - test "exists here too" do - assert_kind_of Admin::WidgetsController, @controller - end -end - -describe Admin::WidgetsController do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end - -describe Admin::WidgetsController, "unauthenticated users" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end - -describe "Admin::WidgetsController" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end - -describe "Admin::WidgetsControllerTest" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end - -describe "Admin::WidgetsController", "unauthenticated users" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end - -describe "Admin::WidgetsControllerTest", "unauthenticated users" do - describe "index" do - it "respond successful" do - assert_kind_of Admin::WidgetsController, @controller - end - end -end diff --git a/actionpack/test/controller/spec_type_test.rb b/actionpack/test/controller/spec_type_test.rb deleted file mode 100644 index 13be8a3405..0000000000 --- a/actionpack/test/controller/spec_type_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require "abstract_unit" - -class ApplicationController < ActionController::Base; end -class ModelsController < ApplicationController; end - -class ActionControllerSpecTypeTest < ActiveSupport::TestCase - def assert_controller actual - assert_equal ActionController::TestCase, actual - end - - def refute_controller actual - refute_equal ActionController::TestCase, actual - end - - def test_spec_type_resolves_for_class_constants - assert_controller MiniTest::Spec.spec_type(ApplicationController) - assert_controller MiniTest::Spec.spec_type(ModelsController) - end - - def test_spec_type_resolves_for_matching_strings - assert_controller MiniTest::Spec.spec_type("WidgetController") - assert_controller MiniTest::Spec.spec_type("WidgetControllerTest") - assert_controller MiniTest::Spec.spec_type("Widget Controller Test") - # And is not case sensitive - assert_controller MiniTest::Spec.spec_type("widgetcontroller") - assert_controller MiniTest::Spec.spec_type("widgetcontrollertest") - assert_controller MiniTest::Spec.spec_type("widget controller test") - end - - def test_spec_type_wont_match_non_space_characters - refute_controller MiniTest::Spec.spec_type("Widget Controller\tTest") - refute_controller MiniTest::Spec.spec_type("Widget Controller\rTest") - refute_controller MiniTest::Spec.spec_type("Widget Controller\nTest") - refute_controller MiniTest::Spec.spec_type("Widget Controller\fTest") - refute_controller MiniTest::Spec.spec_type("Widget ControllerXTest") - end -end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index bdca1d4d77..e4d78d58b9 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -692,7 +692,7 @@ XML assert_equal "bar", @request.params[:foo] @request.recycle! post :no_op - assert_blank @request.params[:foo] + assert @request.params[:foo].blank? end def test_symbolized_path_params_reset_after_request diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index d3fc7128e9..ba24e7fac5 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -2,7 +2,6 @@ require 'abstract_unit' module AbstractController module Testing - class UrlForTest < ActionController::TestCase class W include ActionDispatch::Routing::RouteSet.new.tap { |r| r.draw { get ':controller(/:action(/:id(.:format)))' } }.url_helpers @@ -350,10 +349,10 @@ module AbstractController def test_with_hash_with_indifferent_access W.default_url_options[:controller] = 'd' W.default_url_options[:only_path] = false - assert_equal("/c", W.new.url_for(HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true))) + assert_equal("/c", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'only_path' => true))) W.default_url_options[:action] = 'b' - assert_equal("/c/a", W.new.url_for(HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) + assert_equal("/c/a", W.new.url_for(ActiveSupport::HashWithIndifferentAccess.new('controller' => 'c', 'action' => 'a', 'only_path' => true))) end def test_url_params_with_nil_to_param_are_not_in_url diff --git a/actionpack/test/controller/webservice_test.rb b/actionpack/test/controller/webservice_test.rb index c0b9833603..0480295056 100644 --- a/actionpack/test/controller/webservice_test.rb +++ b/actionpack/test/controller/webservice_test.rb @@ -116,22 +116,22 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_register_and_use_yaml + def test_post_xml_using_a_disallowed_type_attribute + $stderr = StringIO.new with_test_route_set do - with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do - post "/", {"entry" => "loaded from yaml"}.to_yaml, - {'CONTENT_TYPE' => 'application/x-yaml'} + post '/', '<foo type="symbol">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 - assert_equal 'entry', @controller.response.body - assert @controller.params.has_key?(:entry) - assert_equal 'loaded from yaml', @controller.params["entry"] - end + post '/', '<foo type="yaml">value</foo>', 'CONTENT_TYPE' => 'application/xml' + assert_response 500 end + ensure + $stderr = STDERR end - def test_register_and_use_yaml_as_symbol + def test_register_and_use_yaml with_test_route_set do - with_params_parsers Mime::YAML => :yaml do + with_params_parsers Mime::YAML => Proc.new { |d| YAML.load(d) } do post "/", {"entry" => "loaded from yaml"}.to_yaml, {'CONTENT_TYPE' => 'application/x-yaml'} @@ -211,36 +211,6 @@ class WebServiceTest < ActionDispatch::IntegrationTest end end - def test_typecast_as_yaml - with_test_route_set do - with_params_parsers Mime::YAML => :yaml do - yaml = (<<-YAML).strip - --- - data: - a: 15 - b: false - c: true - d: 2005-03-17 - e: 2005-03-17T21:41:07Z - f: unparsed - g: - - 1 - - hello - - 1974-07-25 - YAML - post "/", yaml, {'CONTENT_TYPE' => 'application/x-yaml'} - params = @controller.params - assert_equal 15, params[:data][:a] - assert_equal false, params[:data][:b] - assert_equal true, params[:data][:c] - assert_equal Date.new(2005,3,17), params[:data][:d] - assert_equal Time.utc(2005,3,17,21,41,7), params[:data][:e] - assert_equal "unparsed", params[:data][:f] - assert_equal [1, "hello", Date.new(1974,7,25)], params[:data][:g] - end - end - end - private def with_params_parsers(parsers = {}) old_session = @integration_session diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb index d236b14e02..1319eba9ac 100644 --- a/actionpack/test/dispatch/debug_exceptions_test.rb +++ b/actionpack/test/dispatch/debug_exceptions_test.rb @@ -45,8 +45,17 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest end end - ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false)) - DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true)) + def setup + app = ActiveSupport::OrderedOptions.new + app.config = ActiveSupport::OrderedOptions.new + app.config.assets = ActiveSupport::OrderedOptions.new + app.config.assets.prefix = '/sprockets' + Rails.stubs(:application).returns(app) + end + + RoutesApp = Struct.new(:routes).new(SharedTestRoutes) + ProductionApp = ActionDispatch::DebugExceptions.new(Boomer.new(false), RoutesApp) + DevelopmentApp = ActionDispatch::DebugExceptions.new(Boomer.new(true), RoutesApp) test 'skip diagnosis if not showing detailed exceptions' do @app = ProductionApp @@ -78,6 +87,15 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest assert boomer.closed, "Expected to close the response body" end + test 'displays routes in a table when a RoutingError occurs' do + @app = DevelopmentApp + get "/pass", {}, {'action_dispatch.show_exceptions' => true} + routing_table = body[/route_table.*<.table>/m] + assert_match '/:controller(/:action)(.:format)', routing_table + assert_match ':controller#:action', routing_table + assert_no_match '<|>', routing_table, "there should not be escaped html in the output" + end + test "rescue with diagnostics message" do @app = DevelopmentApp @@ -135,7 +153,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest } }) assert_response 500 - assert_match(/RuntimeError\n in FeaturedTileController/, body) + assert_match(/RuntimeError\n\s+in FeaturedTileController/, body) end test "sets the HTTP charset parameter" do diff --git a/actionpack/test/dispatch/live_response_test.rb b/actionpack/test/dispatch/live_response_test.rb index e16f23914b..e0cfb73acf 100644 --- a/actionpack/test/dispatch/live_response_test.rb +++ b/actionpack/test/dispatch/live_response_test.rb @@ -11,7 +11,7 @@ module ActionController def test_header_merge header = @response.header.merge('Foo' => 'Bar') assert_kind_of(ActionController::Live::Response::Header, header) - refute_equal header, @response.header + assert_not_equal header, @response.header end def test_initialize_with_default_headers diff --git a/actionpack/test/dispatch/request/json_params_parsing_test.rb b/actionpack/test/dispatch/request/json_params_parsing_test.rb index c0c3147e37..7d3fc84089 100644 --- a/actionpack/test/dispatch/request/json_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/json_params_parsing_test.rb @@ -30,6 +30,21 @@ class JsonParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "nils are stripped from collections" do + assert_parses( + {"person" => nil}, + "{\"person\":[null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => ['foo']}, + "{\"person\":[\"foo\",null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + assert_parses( + {"person" => nil}, + "{\"person\":[null, null]}", { 'CONTENT_TYPE' => 'application/json' } + ) + end + test "logs error if parsing unsuccessful" do with_test_routing do output = StringIO.new @@ -107,6 +122,13 @@ class RootLessJSONParamsParsingTest < ActionDispatch::IntegrationTest ) end + test "parses json with non-object JSON content" do + assert_parses( + {"user" => {"_json" => "string content" }, "_json" => "string content" }, + "\"string content\"", { 'CONTENT_TYPE' => 'application/json' } + ) + end + private def assert_parses(expected, actual, headers = {}) with_test_routing(UsersController) do diff --git a/actionpack/test/dispatch/request/query_string_parsing_test.rb b/actionpack/test/dispatch/request/query_string_parsing_test.rb index 3cb430d83d..f072a9f717 100644 --- a/actionpack/test/dispatch/request/query_string_parsing_test.rb +++ b/actionpack/test/dispatch/request/query_string_parsing_test.rb @@ -84,8 +84,8 @@ class QueryStringParsingTest < ActionDispatch::IntegrationTest assert_parses({"action" => nil}, "action") assert_parses({"action" => {"foo" => nil}}, "action[foo]") assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar]") - assert_parses({"action" => {"foo" => { "bar" => [] }}}, "action[foo][bar][]") - assert_parses({"action" => {"foo" => []}}, "action[foo][]") + assert_parses({"action" => {"foo" => { "bar" => nil }}}, "action[foo][bar][]") + assert_parses({"action" => {"foo" => nil }}, "action[foo][]") assert_parses({"action"=>{"foo"=>[{"bar"=>nil}]}}, "action[foo][][bar]") end diff --git a/actionpack/test/dispatch/request/session_test.rb b/actionpack/test/dispatch/request/session_test.rb index 3f36d4f1a9..1517f96fdc 100644 --- a/actionpack/test/dispatch/request/session_test.rb +++ b/actionpack/test/dispatch/request/session_test.rb @@ -24,7 +24,7 @@ module ActionDispatch s['foo'] = 'bar' s1 = Session.create(store, env, {}) - refute_equal s, s1 + assert_not_equal s, s1 assert_equal 'bar', s1['foo'] end diff --git a/actionpack/test/dispatch/request/xml_params_parsing_test.rb b/actionpack/test/dispatch/request/xml_params_parsing_test.rb index cb68667002..f13b64a3c7 100644 --- a/actionpack/test/dispatch/request/xml_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/xml_params_parsing_test.rb @@ -30,6 +30,23 @@ class XmlParamsParsingTest < ActionDispatch::IntegrationTest assert_equal "<ok>bar</ok>", resp.body end + def assert_parses(expected, xml) + with_test_routing do + post "/parse", xml, default_headers + assert_response :ok + assert_equal(expected, TestController.last_request_parameters) + end + end + + test "nils are stripped from collections" do + assert_parses( + {"hash" => { "person" => nil} }, + "<hash><person type=\"array\"><person nil=\"true\"/></person></hash>") + assert_parses( + {"hash" => { "person" => ['foo']} }, + "<hash><person type=\"array\"><person>foo</person><person nil=\"true\"/></person>\n</hash>") + end + test "parses hash params" do with_test_routing do xml = "<person><name>David</name></person>" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 4e59e214c6..02675c7f8c 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -34,7 +34,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal '1.2.3.4', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4,3.4.5.6' - assert_equal '1.2.3.4', request.remote_ip + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'REMOTE_ADDR' => '1.2.3.4', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -47,30 +47,32 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,unknown' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '172.16.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,172.16.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '192.168.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,192.168.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 10.0.0.1, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 10.0.0.1, 10.0.0.1' + assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '127.0.0.1,3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6,127.0.0.1' + assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 172.31.4.4, 10.0.0.1' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -79,26 +81,20 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="1.1.1.1"/, e.message) assert_match(/HTTP_CLIENT_IP="2.2.2.2"/, e.message) + end - # turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip with spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', 'HTTP_CLIENT_IP' => '2.2.2.2', :ip_spoofing_check => false - assert_equal '2.2.2.2', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8' - assert_equal '9.9.9.9', request.remote_ip + assert_equal '1.1.1.1', request.remote_ip end test "remote ip v6" do request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip request = stub_request 'REMOTE_ADDR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', @@ -109,30 +105,26 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,unknown' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '::1, ::1, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, ::1' + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,::1' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329, ::1, fc00::' - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'not_ip_address' assert_equal nil, request.remote_ip + end + test "remote ip v6 spoof detection" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334' e = assert_raise(ActionDispatch::RemoteIp::IpSpoofAttackError) { @@ -141,26 +133,15 @@ class RequestTest < ActiveSupport::TestCase assert_match(/IP spoofing attack/, e.message) assert_match(/HTTP_X_FORWARDED_FOR="fe80:0000:0000:0000:0202:b3ff:fe1e:8329"/, e.message) assert_match(/HTTP_CLIENT_IP="2001:0db8:85a3:0000:0000:8a2e:0370:7334"/, e.message) + end - # Turn IP Spoofing detection off. - # This is useful for sites that are aimed at non-IP clients. The typical - # example is WAP. Since the cellular network is not IP based, it's a - # leap of faith to assume that their proxies are ever going to set the - # HTTP_CLIENT_IP/HTTP_X_FORWARDED_FOR headers properly. + test "remote ip v6 spoof detection disabled" do request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', 'HTTP_CLIENT_IP' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334', :ip_spoofing_check => false - assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip end - test "remote ip when the remote ip middleware returns nil" do - request = stub_request 'REMOTE_ADDR' => '127.0.0.1' - assert_equal '127.0.0.1', request.remote_ip - end - test "remote ip with user specified trusted proxies String" do @trusted_proxies = "67.205.106.73" @@ -170,16 +151,16 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => '172.16.0.1,67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' - assert_equal '172.16.0.1', request.remote_ip + assert_equal '67.205.106.73', request.remote_ip request = stub_request 'REMOTE_ADDR' => '67.205.106.73,3.4.5.6', 'HTTP_X_FORWARDED_FOR' => '67.205.106.73' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73,unknown' assert_equal nil, request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' assert_equal '3.4.5.6', request.remote_ip end @@ -196,13 +177,13 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'REMOTE_ADDR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,::1', 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' - assert_equal 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329', request.remote_ip + assert_equal '::1', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal nil, request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329,2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + assert_equal "2001:0db8:85a3:0000:0000:8a2e:0370:7334", request.remote_ip end test "remote ip with user specified trusted proxies Regexp" do @@ -212,8 +193,8 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' assert_equal '3.4.5.6', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '67.205.106.73, 10.0.0.1, 9.9.9.9, 3.4.5.6' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '10.0.0.1, 9.9.9.9, 3.4.5.6, 67.205.106.73' + assert_equal '3.4.5.6', request.remote_ip end test "remote ip v6 with user specified trusted proxies Regexp" do @@ -223,8 +204,13 @@ class RequestTest < ActiveSupport::TestCase 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329' assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => 'fe80:0000:0000:0000:0202:b3ff:fe1e:8329, 2001:0db8:85a3:0000:0000:8a2e:0370:7334' - assert_equal nil, request.remote_ip + request = stub_request 'HTTP_X_FORWARDED_FOR' => '2001:0db8:85a3:0000:0000:8a2e:0370:7334, fe80:0000:0000:0000:0202:b3ff:fe1e:8329' + assert_equal '2001:0db8:85a3:0000:0000:8a2e:0370:7334', request.remote_ip + end + + test "remote ip middleware not present still returns an IP" do + request = ActionDispatch::Request.new({'REMOTE_ADDR' => '127.0.0.1'}) + assert_equal '127.0.0.1', request.remote_ip end test "domains" do @@ -602,7 +588,7 @@ class RequestTest < ActiveSupport::TestCase assert_equal request.format.xml?, false assert_equal request.format.json?, false end - + test "formats with xhr request" do request = stub_request 'HTTP_X_REQUESTED_WITH' => "XMLHttpRequest" request.expects(:parameters).at_least_once.returns({}) diff --git a/actionpack/test/dispatch/routing/inspector_test.rb b/actionpack/test/dispatch/routing/inspector_test.rb index a3034ce001..c7dcb5a683 100644 --- a/actionpack/test/dispatch/routing/inspector_test.rb +++ b/actionpack/test/dispatch/routing/inspector_test.rb @@ -1,5 +1,4 @@ -require 'minitest/autorun' -require 'action_controller' +require 'abstract_unit' require 'rails/engine' require 'action_dispatch/routing/inspector' @@ -8,7 +7,6 @@ module ActionDispatch class RoutesInspectorTest < ActiveSupport::TestCase def setup @set = ActionDispatch::Routing::RouteSet.new - @inspector = ActionDispatch::Routing::RoutesInspector.new app = ActiveSupport::OrderedOptions.new app.config = ActiveSupport::OrderedOptions.new app.config.assets = ActiveSupport::OrderedOptions.new @@ -17,9 +15,10 @@ module ActionDispatch Rails.stubs(:env).returns("development") end - def draw(&block) + def draw(options = {}, &block) @set.draw(&block) - @inspector.format(@set.routes) + inspector = ActionDispatch::Routing::RoutesInspector.new(@set.routes) + inspector.format(ActionDispatch::Routing::ConsoleFormatter.new, options[:filter]).split("\n") end def test_displaying_routes_for_engines @@ -40,7 +39,8 @@ module ActionDispatch expected = [ "custom_assets GET /custom/assets(.:format) custom_assets#show", " blog /blog Blog::Engine", - "\nRoutes for Blog::Engine:", + "", + "Routes for Blog::Engine:", "cart GET /cart(.:format) cart#show" ] assert_equal expected, output @@ -165,6 +165,22 @@ module ActionDispatch assert_equal " bar GET /bar(.:format) redirect(307, path: /foo/bar)", output[1] assert_equal "foobar GET /foobar(.:format) redirect(301)", output[2] end + + def test_routes_can_be_filtered + output = draw(filter: 'posts') do + resources :articles + resources :posts + end + + assert_equal [" posts GET /posts(.:format) posts#index", + " POST /posts(.:format) posts#create", + " new_post GET /posts/new(.:format) posts#new", + "edit_post GET /posts/:id/edit(.:format) posts#edit", + " post GET /posts/:id(.:format) posts#show", + " PATCH /posts/:id(.:format) posts#update", + " PUT /posts/:id(.:format) posts#update", + " DELETE /posts/:id(.:format) posts#destroy"], output + end end end end diff --git a/actionpack/test/dispatch/session/abstract_store_test.rb b/actionpack/test/dispatch/session/abstract_store_test.rb index 8daf3d3f5e..fe1a7b4f86 100644 --- a/actionpack/test/dispatch/session/abstract_store_test.rb +++ b/actionpack/test/dispatch/session/abstract_store_test.rb @@ -42,7 +42,7 @@ module ActionDispatch as.call(@env) session1 = Request::Session.find @env - refute_equal session, session1 + assert_not_equal session, session1 assert_equal session.to_hash, session1.to_hash end diff --git a/actionpack/test/dispatch/spec_type_test.rb b/actionpack/test/dispatch/spec_type_test.rb deleted file mode 100644 index 6cd19fd333..0000000000 --- a/actionpack/test/dispatch/spec_type_test.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "abstract_unit" - -class SpecTypeTest < ActiveSupport::TestCase - def assert_dispatch actual - assert_equal ActionDispatch::IntegrationTest, actual - end - - def refute_dispatch actual - refute_equal ActionDispatch::IntegrationTest, actual - end - - def test_spec_type_resolves_for_matching_acceptance_strings - assert_dispatch MiniTest::Spec.spec_type("WidgetAcceptanceTest") - assert_dispatch MiniTest::Spec.spec_type("Widget Acceptance Test") - assert_dispatch MiniTest::Spec.spec_type("widgetacceptancetest") - assert_dispatch MiniTest::Spec.spec_type("widget acceptance test") - end - - def test_spec_type_wont_match_non_space_characters_acceptance - refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\tTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\rTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\nTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Acceptance\fTest") - refute_dispatch MiniTest::Spec.spec_type("Widget AcceptanceXTest") - end - - def test_spec_type_resolves_for_matching_integration_strings - assert_dispatch MiniTest::Spec.spec_type("WidgetIntegrationTest") - assert_dispatch MiniTest::Spec.spec_type("Widget Integration Test") - assert_dispatch MiniTest::Spec.spec_type("widgetintegrationtest") - assert_dispatch MiniTest::Spec.spec_type("widget integration test") - end - - def test_spec_type_wont_match_non_space_characters_integration - refute_dispatch MiniTest::Spec.spec_type("Widget Integration\tTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Integration\rTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Integration\nTest") - refute_dispatch MiniTest::Spec.spec_type("Widget Integration\fTest") - refute_dispatch MiniTest::Spec.spec_type("Widget IntegrationXTest") - end -end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb index 6f075a9074..a9bea7ea73 100644 --- a/actionpack/test/dispatch/ssl_test.rb +++ b/actionpack/test/dispatch/ssl_test.rb @@ -47,7 +47,7 @@ class SSLTest < ActionDispatch::IntegrationTest def test_disable_hsts_header self.app = ActionDispatch::SSL.new(default_app, :hsts => false) get "https://example.org/" - refute response.headers['Strict-Transport-Security'] + assert_not response.headers['Strict-Transport-Security'] end def test_hsts_expires @@ -57,6 +57,13 @@ class SSLTest < ActionDispatch::IntegrationTest response.headers['Strict-Transport-Security'] end + def test_hsts_expires_with_duration + self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 1.year }) + get "https://example.org/" + assert_equal "max-age=31557600", + response.headers['Strict-Transport-Security'] + end + def test_hsts_include_subdomains self.app = ActionDispatch::SSL.new(default_app, :hsts => { :subdomains => true }) get "https://example.org/" diff --git a/actionpack/test/fixtures/test/_partial_name_local_variable.erb b/actionpack/test/fixtures/test/_partial_name_local_variable.erb new file mode 100644 index 0000000000..cc3a91c89f --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_name_local_variable.erb @@ -0,0 +1 @@ +<%= partial_name_local_variable %> diff --git a/actionpack/test/journey/gtg/builder_test.rb b/actionpack/test/journey/gtg/builder_test.rb index a633c3eea6..c1da374007 100644 --- a/actionpack/test/journey/gtg/builder_test.rb +++ b/actionpack/test/journey/gtg/builder_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module GTG - class TestBuilder < MiniTest::Unit::TestCase + class TestBuilder < ActiveSupport::TestCase def test_following_states_multi table = tt ['a|a'] assert_equal 1, table.move([0], 'a').length diff --git a/actionpack/test/journey/gtg/transition_table_test.rb b/actionpack/test/journey/gtg/transition_table_test.rb index 6d81b72c41..33acba8b65 100644 --- a/actionpack/test/journey/gtg/transition_table_test.rb +++ b/actionpack/test/journey/gtg/transition_table_test.rb @@ -4,7 +4,7 @@ require 'json' module ActionDispatch module Journey module GTG - class TestGeneralizedTable < MiniTest::Unit::TestCase + class TestGeneralizedTable < ActiveSupport::TestCase def test_to_json table = tt %w{ /articles(.:format) @@ -29,7 +29,7 @@ module ActionDispatch } svg = table.to_svg assert svg - refute_match(/DOCTYPE/, svg) + assert_no_match(/DOCTYPE/, svg) end end @@ -53,7 +53,7 @@ module ActionDispatch sim = simulator_for ['/foo(/bar)'] assert_match sim, '/foo' assert_match sim, '/foo/bar' - refute_match sim, '/foo/' + assert_no_match sim, '/foo/' end def test_match_data diff --git a/actionpack/test/journey/nfa/simulator_test.rb b/actionpack/test/journey/nfa/simulator_test.rb index 9f89329b57..673a491fe5 100644 --- a/actionpack/test/journey/nfa/simulator_test.rb +++ b/actionpack/test/journey/nfa/simulator_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module NFA - class TestSimulator < MiniTest::Unit::TestCase + class TestSimulator < ActiveSupport::TestCase def test_simulate_simple sim = simulator_for ['/foo'] assert_match sim, '/foo' @@ -11,17 +11,17 @@ module ActionDispatch def test_simulate_simple_no_match sim = simulator_for ['/foo'] - refute_match sim, 'foo' + assert_no_match sim, 'foo' end def test_simulate_simple_no_match_too_long sim = simulator_for ['/foo'] - refute_match sim, '/foo/bar' + assert_no_match sim, '/foo/bar' end def test_simulate_simple_no_match_wrong_string sim = simulator_for ['/foo'] - refute_match sim, '/bar' + assert_no_match sim, '/bar' end def test_simulate_regex @@ -34,14 +34,14 @@ module ActionDispatch sim = simulator_for ['/foo', '/bar'] assert_match sim, '/bar' assert_match sim, '/foo' - refute_match sim, '/baz' + assert_no_match sim, '/baz' end def test_simulate_optional sim = simulator_for ['/foo(/bar)'] assert_match sim, '/foo' assert_match sim, '/foo/bar' - refute_match sim, '/foo/' + assert_no_match sim, '/foo/' end def test_matchdata_has_memos diff --git a/actionpack/test/journey/nfa/transition_table_test.rb b/actionpack/test/journey/nfa/transition_table_test.rb index 72cefe42bf..1248082c03 100644 --- a/actionpack/test/journey/nfa/transition_table_test.rb +++ b/actionpack/test/journey/nfa/transition_table_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module NFA - class TestTransitionTable < MiniTest::Unit::TestCase + class TestTransitionTable < ActiveSupport::TestCase def setup @parser = Journey::Parser.new end diff --git a/actionpack/test/journey/nodes/symbol_test.rb b/actionpack/test/journey/nodes/symbol_test.rb index f53840274a..d411a5018a 100644 --- a/actionpack/test/journey/nodes/symbol_test.rb +++ b/actionpack/test/journey/nodes/symbol_test.rb @@ -3,13 +3,13 @@ require 'abstract_unit' module ActionDispatch module Journey module Nodes - class TestSymbol < MiniTest::Unit::TestCase + class TestSymbol < ActiveSupport::TestCase def test_default_regexp? sym = Symbol.new nil assert sym.default_regexp? sym.regexp = nil - refute sym.default_regexp? + assert_not sym.default_regexp? end end end diff --git a/actionpack/test/journey/path/pattern_test.rb b/actionpack/test/journey/path/pattern_test.rb index 0f2d0d44c0..2b7227cd0d 100644 --- a/actionpack/test/journey/path/pattern_test.rb +++ b/actionpack/test/journey/path/pattern_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module Path - class TestPattern < MiniTest::Unit::TestCase + class TestPattern < ActiveSupport::TestCase x = /.+/ { '/:controller(/:action)' => %r{\A/(#{x})(?:/([^/.?]+))?\Z}, @@ -88,7 +88,7 @@ module ActionDispatch path = Pattern.new strexp assert_match(path, '/page/tender') assert_match(path, '/page/love') - refute_match(path, '/page/loving') + assert_no_match(path, '/page/loving') end def test_optional_names @@ -110,7 +110,7 @@ module ActionDispatch ) path = Pattern.new strexp assert_match(path, '/123') - refute_match(path, '/') + assert_no_match(path, '/') end def test_to_regexp_with_group @@ -122,7 +122,7 @@ module ActionDispatch path = Pattern.new strexp assert_match(path, '/page/tender') assert_match(path, '/page/love') - refute_match(path, '/page/loving') + assert_no_match(path, '/page/loving') end def test_ast_sets_regular_expressions @@ -189,7 +189,7 @@ module ActionDispatch path = Pattern.new strexp assert_match(path, '/page/TENDER/aaron') assert_match(path, '/page/loVE/aaron') - refute_match(path, '/page/loVE/AAron') + assert_no_match(path, '/page/loVE/AAron') end def test_to_regexp_with_strexp @@ -210,7 +210,7 @@ module ActionDispatch path = Pattern.new '/:controller(/:action(/:id(.:format)))' uri = 'content' - refute path =~ uri + assert_not path =~ uri end def test_match_controller diff --git a/actionpack/test/journey/route/definition/parser_test.rb b/actionpack/test/journey/route/definition/parser_test.rb index 580235c6a1..d7d7172a40 100644 --- a/actionpack/test/journey/route/definition/parser_test.rb +++ b/actionpack/test/journey/route/definition/parser_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module Definition - class TestParser < MiniTest::Unit::TestCase + class TestParser < ActiveSupport::TestCase def setup @parser = Parser.new end diff --git a/actionpack/test/journey/route/definition/scanner_test.rb b/actionpack/test/journey/route/definition/scanner_test.rb index 110baf9977..624e6df51a 100644 --- a/actionpack/test/journey/route/definition/scanner_test.rb +++ b/actionpack/test/journey/route/definition/scanner_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey module Definition - class TestScanner < MiniTest::Unit::TestCase + class TestScanner < ActiveSupport::TestCase def setup @scanner = Scanner.new end diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index b205db5fbc..78608a5c6b 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' module ActionDispatch module Journey - class TestRoute < MiniTest::Unit::TestCase + class TestRoute < ActiveSupport::TestCase def test_initialize app = Object.new path = Path::Pattern.new '/:controller(/:action(/:id(.:format)))' @@ -92,7 +92,7 @@ module ActionDispatch routes = [specific, generic] - refute_equal specific.score(knowledge), generic.score(knowledge) + assert_not_equal specific.score(knowledge), generic.score(knowledge) found = routes.sort_by { |r| r.score(knowledge) }.last diff --git a/actionpack/test/journey/router/strexp_test.rb b/actionpack/test/journey/router/strexp_test.rb index 9e0337f144..7ccdfb7b4d 100644 --- a/actionpack/test/journey/router/strexp_test.rb +++ b/actionpack/test/journey/router/strexp_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey class Router - class TestStrexp < MiniTest::Unit::TestCase + class TestStrexp < ActiveSupport::TestCase def test_many_names exp = Strexp.new( "/:controller(/:action(/:id(.:format)))", diff --git a/actionpack/test/journey/router/utils_test.rb b/actionpack/test/journey/router/utils_test.rb index 97a6449c99..057dc40cca 100644 --- a/actionpack/test/journey/router/utils_test.rb +++ b/actionpack/test/journey/router/utils_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey class Router - class TestUtils < MiniTest::Unit::TestCase + class TestUtils < ActiveSupport::TestCase def test_path_escape assert_equal "a/b%20c+d", Utils.escape_path("a/b c+d") end diff --git a/actionpack/test/journey/router_test.rb b/actionpack/test/journey/router_test.rb index 1b64600ba8..27bdb0108a 100644 --- a/actionpack/test/journey/router_test.rb +++ b/actionpack/test/journey/router_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module ActionDispatch module Journey - class TestRouter < MiniTest::Unit::TestCase + class TestRouter < ActiveSupport::TestCase attr_reader :routes def setup @@ -277,7 +277,7 @@ module ActionDispatch @router.recognize(env) do |*whatever| yielded = true end - refute yielded + assert_not yielded end def test_required_part_in_recall diff --git a/actionpack/test/journey/routes_test.rb b/actionpack/test/journey/routes_test.rb index 3b17bd53b7..25e0321d31 100644 --- a/actionpack/test/journey/routes_test.rb +++ b/actionpack/test/journey/routes_test.rb @@ -2,7 +2,7 @@ require 'abstract_unit' module ActionDispatch module Journey - class TestRoutes < MiniTest::Unit::TestCase + class TestRoutes < ActiveSupport::TestCase def test_clear routes = Routes.new exp = Router::Strexp.new '/foo(/:id)', {}, ['/.?'] @@ -23,7 +23,7 @@ module ActionDispatch routes.add_route nil, path, {}, {}, {} ast = routes.ast routes.add_route nil, path, {}, {}, {} - refute_equal ast, routes.ast + assert_not_equal ast, routes.ast end def test_simulator_changes @@ -33,7 +33,7 @@ module ActionDispatch routes.add_route nil, path, {}, {}, {} sim = routes.simulator routes.add_route nil, path, {}, {}, {} - refute_equal sim, routes.simulator + assert_not_equal sim, routes.simulator end def test_first_name_wins diff --git a/actionpack/test/template/atom_feed_helper_test.rb b/actionpack/test/template/atom_feed_helper_test.rb index 89aae4ac56..63b5ac0fab 100644 --- a/actionpack/test/template/atom_feed_helper_test.rb +++ b/actionpack/test/template/atom_feed_helper_test.rb @@ -238,7 +238,7 @@ class AtomFeedTest < ActionController::TestCase get :index, :id=>"provide_builder" # because we pass in the non-default builder, the content generated by the # helper should go 'nowhere'. Leaving the response body blank. - assert_blank @response.body + assert @response.body.blank? end end diff --git a/actionpack/test/template/digestor_test.rb b/actionpack/test/template/digestor_test.rb index 02b1fd87a8..849e2981a6 100644 --- a/actionpack/test/template/digestor_test.rb +++ b/actionpack/test/template/digestor_test.rb @@ -138,6 +138,20 @@ class TemplateDigestorTest < ActionView::TestCase end end + def test_dependencies_via_options_results_in_different_digest + digest_plain = digest("comments/_comment") + digest_fridge = digest("comments/_comment", dependencies: ["fridge"]) + digest_phone = digest("comments/_comment", dependencies: ["phone"]) + digest_fridge_phone = digest("comments/_comment", dependencies: ["fridge", "phone"]) + + assert_not_equal digest_plain, digest_fridge + assert_not_equal digest_plain, digest_phone + assert_not_equal digest_plain, digest_fridge_phone + assert_not_equal digest_fridge, digest_phone + assert_not_equal digest_fridge, digest_fridge_phone + assert_not_equal digest_phone, digest_fridge_phone + end + private def assert_logged(message) old_logger = ActionView::Base.logger @@ -164,8 +178,8 @@ class TemplateDigestorTest < ActionView::TestCase ActionView::Digestor.cache.clear end - def digest(template_name) - ActionView::Digestor.digest(template_name, :html, FixtureFinder.new) + def digest(template_name, options={}) + ActionView::Digestor.digest(template_name, :html, FixtureFinder.new, options) end def change_template(template_name) diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb index c73e80ed88..2131f81396 100644 --- a/actionpack/test/template/form_collections_helper_test.rb +++ b/actionpack/test/template/form_collections_helper_test.rb @@ -149,6 +149,12 @@ class FormCollectionsHelperTest < ActionView::TestCase assert_select 'label[for=post_category_id_2]', 'Category 2' end + test 'collection radio accepts checked item which has a value of false' do + with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => false + assert_no_select 'input[type=radio][value=true][checked=checked]' + assert_select 'input[type=radio][value=false][checked=checked]' + end + # COLLECTION CHECK BOXES test 'collection check boxes accepts a collection and generate a serie of checkboxes for value method' do collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index cc098b6be7..247068de4e 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -1171,7 +1171,6 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end - def test_form_for_with_format form_for(@post, :format => :json, :html => { :id => "edit_post_123", :class => "edit_post" }) do |f| concat f.label(:title) @@ -2716,6 +2715,19 @@ class FormHelperTest < ActionView::TestCase end end + def test_form_for_only_instantiates_builder_once + initialization_count = 0 + builder_class = Class.new(ActionView::Helpers::FormBuilder) do + define_method :initialize do |*args| + super(*args) + initialization_count += 1 + end + end + + form_for(@post, builder: builder_class) { } + assert_equal 1, initialization_count, 'form builder instantiated more than once' + end + protected def hidden_fields(method = nil) diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 0a94fa079b..6c6a142397 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -488,7 +488,7 @@ class FormTagHelperTest < ActionView::TestCase def test_image_submit_tag_with_confirmation assert_dom_equal( - %(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />), + %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />), image_submit_tag("save.gif", :data => { :confirm => "Are you sure?" }) ) end @@ -496,7 +496,7 @@ class FormTagHelperTest < ActionView::TestCase def test_image_submit_tag_with_deprecated_confirmation assert_deprecated ":confirm option is deprecated and will be removed from Rails 4.1. Use 'data: { confirm: \'Text\' }' instead" do assert_dom_equal( - %(<input type="image" src="/images/save.gif" data-confirm="Are you sure?" />), + %(<input alt="Save" type="image" src="/images/save.gif" data-confirm="Are you sure?" />), image_submit_tag("save.gif", :confirm => "Are you sure?") ) end diff --git a/actionpack/test/template/record_tag_helper_test.rb b/actionpack/test/template/record_tag_helper_test.rb index a84034c02e..9c49438f6a 100644 --- a/actionpack/test/template/record_tag_helper_test.rb +++ b/actionpack/test/template/record_tag_helper_test.rb @@ -25,33 +25,33 @@ class RecordTagHelperTest < ActionView::TestCase def test_content_tag_for expected = %(<li class="record_tag_post" id="record_tag_post_45"></li>) - actual = content_tag_for(:li, @post) { } + actual = content_tag_for(:li, @post) assert_dom_equal expected, actual end def test_content_tag_for_prefix expected = %(<ul class="archived_record_tag_post" id="archived_record_tag_post_45"></ul>) - actual = content_tag_for(:ul, @post, :archived) { } + actual = content_tag_for(:ul, @post, :archived) assert_dom_equal expected, actual end def test_content_tag_for_with_extra_html_options expected = %(<tr class="record_tag_post special" id="record_tag_post_45" style='background-color: #f0f0f0'></tr>) - actual = content_tag_for(:tr, @post, :class => "special", :style => "background-color: #f0f0f0") { } + actual = content_tag_for(:tr, @post, class: "special", style: "background-color: #f0f0f0") assert_dom_equal expected, actual end def test_content_tag_for_with_prefix_and_extra_html_options expected = %(<tr class="archived_record_tag_post special" id="archived_record_tag_post_45" style='background-color: #f0f0f0'></tr>) - actual = content_tag_for(:tr, @post, :archived, :class => "special", :style => "background-color: #f0f0f0") { } + actual = content_tag_for(:tr, @post, :archived, class: "special", style: "background-color: #f0f0f0") assert_dom_equal expected, actual end def test_block_not_in_erb_multiple_calls expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = div_for(@post, :class => "special") { @post.body } + actual = div_for(@post, class: "special") { @post.body } assert_dom_equal expected, actual - actual = div_for(@post, :class => "special") { @post.body } + actual = div_for(@post, class: "special") { @post.body } assert_dom_equal expected, actual end @@ -63,7 +63,7 @@ class RecordTagHelperTest < ActionView::TestCase def test_div_for_in_erb expected = %(<div class="record_tag_post special" id="record_tag_post_45">What a wonderful world!</div>) - actual = render_erb("<%= div_for(@post, :class => 'special') do %><%= @post.body %><% end %>") + actual = render_erb("<%= div_for(@post, class: 'special') do %><%= @post.body %><% end %>") assert_dom_equal expected, actual end @@ -75,6 +75,14 @@ class RecordTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_content_tag_for_collection_without_given_block + post_1 = RecordTagPost.new.tap { |post| post.id = 101; post.body = "Hello!" } + post_2 = RecordTagPost.new.tap { |post| post.id = 102; post.body = "World!" } + expected = %(<li class="record_tag_post" id="record_tag_post_101"></li>\n<li class="record_tag_post" id="record_tag_post_102"></li>) + actual = content_tag_for(:li, [post_1, post_2]) + assert_dom_equal expected, actual + end + def test_div_for_collection post_1 = RecordTagPost.new { |post| post.id = 101; post.body = "Hello!" } post_2 = RecordTagPost.new { |post| post.id = 102; post.body = "World!" } @@ -84,7 +92,7 @@ class RecordTagHelperTest < ActionView::TestCase end def test_content_tag_for_single_record_is_html_safe - result = div_for(@post, :class => "special") { @post.body } + result = div_for(@post, class: "special") { @post.body } assert result.html_safe? end @@ -96,8 +104,8 @@ class RecordTagHelperTest < ActionView::TestCase end def test_content_tag_for_does_not_change_options_hash - options = { :class => "important" } - content_tag_for(:li, @post, options) { } - assert_equal({ :class => "important" }, options) + options = { class: "important" } + content_tag_for(:li, @post, options) + assert_equal({ class: "important" }, options) end end diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 9fb26e32b1..8111e58527 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -318,6 +318,13 @@ module RenderTestCases @controller_view.render(customers, :greeting => "Hello") end + def test_render_partial_without_object_or_collection_does_not_generate_partial_name_local_variable + exception = assert_raises ActionView::Template::Error do + @controller_view.render("partial_name_local_variable") + end + assert_match "undefined local variable or method `partial_name_local_variable'", exception.message + end + # TODO: The reason for this test is unclear, improve documentation def test_render_partial_and_fallback_to_layout assert_equal "Before (Josh)\n\nAfter", @view.render(:partial => "test/layout_for_partial", :locals => { :name => "Josh" }) diff --git a/actionpack/test/template/spec_type_test.rb b/actionpack/test/template/spec_type_test.rb deleted file mode 100644 index 08a7bdf81d..0000000000 --- a/actionpack/test/template/spec_type_test.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'abstract_unit' - -class ActionViewSpecTypeTest < ActiveSupport::TestCase - def assert_view actual - assert_equal ActionView::TestCase, actual - end - - def refute_view actual - refute_equal ActionView::TestCase, actual - end - - def test_spec_type_resolves_for_matching_helper_strings - assert_view MiniTest::Spec.spec_type("WidgetHelper") - assert_view MiniTest::Spec.spec_type("WidgetHelperTest") - assert_view MiniTest::Spec.spec_type("Widget Helper Test") - # And is not case sensitive - assert_view MiniTest::Spec.spec_type("widgethelper") - assert_view MiniTest::Spec.spec_type("widgethelpertest") - assert_view MiniTest::Spec.spec_type("widget helper test") - end - - def test_spec_type_resolves_for_matching_view_strings - assert_view MiniTest::Spec.spec_type("WidgetView") - assert_view MiniTest::Spec.spec_type("WidgetViewTest") - assert_view MiniTest::Spec.spec_type("Widget View Test") - # And is not case sensitive - assert_view MiniTest::Spec.spec_type("widgetview") - assert_view MiniTest::Spec.spec_type("widgetviewtest") - assert_view MiniTest::Spec.spec_type("widget view test") - end - - def test_spec_type_wont_match_non_space_characters - refute_view MiniTest::Spec.spec_type("Widget Helper\tTest") - refute_view MiniTest::Spec.spec_type("Widget Helper\rTest") - refute_view MiniTest::Spec.spec_type("Widget Helper\nTest") - refute_view MiniTest::Spec.spec_type("Widget Helper\fTest") - refute_view MiniTest::Spec.spec_type("Widget HelperXTest") - end -end diff --git a/actionpack/test/template/test_test.rb b/actionpack/test/template/test_test.rb index e843a1deb4..108a674d95 100644 --- a/actionpack/test/template/test_test.rb +++ b/actionpack/test/template/test_test.rb @@ -78,59 +78,3 @@ class CrazyStringHelperTest < ActionView::TestCase assert_equal PeopleHelper, self.class.helper_class end end - -describe PeopleHelper do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end -end - -describe PeopleHelper, :helper_class do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end -end - -describe PeopleHelper do - describe "even while nested" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end - end -end - -describe PeopleHelper, :helper_class do - describe "even while nested" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end - end -end - -describe "PeopleHelper" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end -end - -describe "PeopleHelperTest" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end -end - -describe "PeopleHelper" do - describe "even while nested" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end - end -end - -describe "PeopleHelperTest" do - describe "even while nested" do - it "resolves the right helper_class" do - assert_equal PeopleHelper, self.class.helper_class - end - end -end diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb index c44c5d8968..55620abe84 100644 --- a/actionpack/test/ts_isolated.rb +++ b/actionpack/test/ts_isolated.rb @@ -1,4 +1,4 @@ -require 'minitest/autorun' +require 'active_support/testing/autorun' require 'rbconfig' require 'abstract_unit' |