diff options
Diffstat (limited to 'actionpack')
179 files changed, 5094 insertions, 3256 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 0dafef90ae..f004a4fce7 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,10 +1,109 @@ ## Rails 4.0.0 (unreleased) ## +* 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 + +* 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* + +* Removed default `size` option from the `text_field`, `search_field`, `telephone_field`, `url_field`, `email_field` helpers. *Philip Arndt* + +* Removed default `cols` and `rows` options from the `text_area` helper. *Philip Arndt* + +* Adds support for layouts when rendering a partial with a given collection. *serabe* + +* Allows the route helper `root` to take a string argument. For example, `root 'pages#main'`. *bcardarella* + +* Forms of persisted records use always PATCH (via the `_method` hack). *fxn* + +* For resources, both PATCH and PUT are routed to the `update` action. *fxn* + +* Don't ignore `force_ssl` in development. This is a change of behavior - use a `:if` condition to recreate the old behavior. + + class AccountsController < ApplicationController + force_ssl :if => :ssl_configured? + + def ssl_configured? + !Rails.env.development? + end + end + + *Pat Allan* + +* Adds support for the PATCH verb: + * Request objects respond to `patch?`. + * Routes have a new `patch` method, and understand `:patch` in the + existing places where a verb is configured, like `:via`. + * New method `patch` available in functional tests. + * If `:patch` is the default verb for updates, edits are + tunneled as PATCH rather than as PUT, and routing acts accordingly. + * New method `patch_via_redirect` available in integration tests. + + *dlee* + +* Integration tests support the `OPTIONS` method. *Jeremy Kemper* + +* `expires_in` accepts a `must_revalidate` flag. If true, "must-revalidate" + is added to the Cache-Control header. *fxn* + +* Add `date_field` and `date_field_tag` helpers which render an `input[type="date"]` tag *Olek Janiszewski* + +* Adds `image_url`, `javascript_url`, `stylesheet_url`, `audio_url`, `video_url`, and `font_url` + to assets tag helper. These URL helpers will return the full path to your assets. This is useful + when you are going to reference this asset from external host. *Prem Sichanugrist* + +* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist* + +* Allow `value_method` and `text_method` arguments from `collection_select` and + `options_from_collection_for_select` to receive an object that responds to `:call`, + such as a `proc`, to evaluate the option in the current element context. This works + the same way with `collection_radio_buttons` and `collection_check_boxes`. + + *Carlos Antonio da Silva + Rafael Mendonça França* + +* Add `collection_check_boxes` form helper, similar to `collection_select`: + Example: + + collection_check_boxes :post, :author_ids, Author.all, :id, :name + # Outputs something like: + <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" /> + <label for="post_author_ids_1">D. Heinemeier Hansson</label> + <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> + <label for="post_author_ids_2">D. Thomas</label> + <input name="post[author_ids][]" type="hidden" value="" /> + + The label/check_box pairs can be customized with a block. + + *Carlos Antonio da Silva + Rafael Mendonça França* + +* Add `collection_radio_buttons` form helper, similar to `collection_select`: + Example: + + collection_radio_buttons :post, :author_id, Author.all, :id, :name + # Outputs something like: + <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" /> + <label for="post_author_id_1">D. Heinemeier Hansson</label> + <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> + <label for="post_author_id_2">D. Thomas</label> + + The label/radio_button pairs can be customized with a block. + + *Carlos Antonio da Silva + Rafael Mendonça França* + +* check_box with `:form` html5 attribute will now replicate the `:form` + attribute to the hidden field as well. *Carlos Antonio da Silva* + +* Turn off verbose mode of rack-cache, we still have X-Rack-Cache to + check that info. Closes #5245. *Santiago Pastorino* + * `label` form helper accepts :for => nil to not generate the attribute. *Carlos Antonio da Silva* * Add `:format` option to number_to_percentage *Rodrigo Flores* -* Add `config.action_view.logger` to configure logger for ActionView. *Rafael França* +* Add `config.action_view.logger` to configure logger for ActionView. *Rafael Mendonça França* * Deprecated ActionController::Integration in favour of ActionDispatch::Integration @@ -28,6 +127,51 @@ * `favicon_link_tag` helper will now use the favicon in app/assets by default. *Lucas Caton* +* `ActionView::Helpers::TextHelper#highlight` now defaults to the + HTML5 `mark` element. *Brian Cardarella* + + +## Rails 3.2.3 (unreleased) ## + +* Add `config.action_view.embed_authenticity_token_in_remote_forms` (defaults to true) which allows to set if authenticity token will be included by default in remote forms. If you change it to false, you can still force authenticity token by passing `:authenticity_token => true` in form options *Piotr Sarnacki* + +* Do not include the authenticity token in forms where remote: true as ajax forms use the meta-tag value *DHH* + +* Upgrade rack-cache to 1.2. *José Valim* + +* ActionController::SessionManagement is removed. *Santiago Pastorino* + +* Since the router holds references to many parts of the system like engines, controllers and the application itself, inspecting the route set can actually be really slow, therefore we default alias inspect to to_s. *José Valim* + +* Add a new line after the textarea opening tag. Closes #393 *Rafael Mendonça França* + +* Always pass a respond block from to responder. We should let the responder decide what to do with the given overridden response block, and not short circuit it. *Prem Sichanugrist* + +* Fixes layout rendering regression from 3.2.2. *José Valim* + + +## Rails 3.2.2 (March 1, 2012) ## + +* Format lookup for partials is derived from the format in which the template is being rendered. Closes #5025 part 2 *Santiago Pastorino* + +* Use the right format when a partial is missing. Closes #5025. *Santiago Pastorino* + +* Default responder will now always use your overridden block in `respond_with` to render your response. *Prem Sichanugrist* + +* check_box helper with :disabled => true will generate a disabled hidden field to conform with the HTML convention where disabled fields are not submitted with the form. + This is a behavior change, previously the hidden tag had a value of the disabled checkbox. + *Tadas Tamosauskas* + + +## Rails 3.2.1 (January 26, 2012) ## + +* Documentation improvements. + +* Allow `form.select` to accept ranges (regression). *Jeremy Walker* + +* `datetime_select` works with -/+ infinity dates. *Joe Van Dyk* + + ## Rails 3.2.0 (January 20, 2012) ## * Add `config.action_dispatch.default_charset` to configure default charset for ActionDispatch::Response. *Carlos Antonio da Silva* @@ -196,21 +340,34 @@ returned by the class method attribute_names will be wrapped. This fixes the wrapping of nested attributes by adding them to attr_accessible. -## Rails 3.1.4 (unreleased) ## + +## Rails 3.1.4 (March 1, 2012) ## + +* Skip assets group in Gemfile and all assets configurations options + when the application is generated with --skip-sprockets option. + + *Guillermo Iguaran* + +* Use ProcessedAsset#pathname in Sprockets helpers when debugging is on. Closes #3333 #3348 #3361. + + *Guillermo Iguaran* * Allow to use asset_path on named_routes aliasing RailsHelper's asset_path to path_to_asset *Adrian Pike* -* Assets should use the request protocol by default or default to - relative if no request is available *Jonathan del Strother* +* Assets should use the request protocol by default or default to relative if no request is available *Jonathan del Strother* + ## Rails 3.1.3 (November 20, 2011) ## +* Downgrade sprockets to ~> 2.0.3. Using 2.1.0 caused regressions. + * Fix using `translate` helper with a html translation which uses the `:count` option for pluralization. *Jon Leighton* + ## Rails 3.1.2 (November 18, 2011) ## * Fix XSS security vulnerability in the `translate` helper method. When using interpolation @@ -252,6 +409,7 @@ * Ensure users upgrading from 3.0.x to 3.1.x will properly upgrade their flash object in session (issues #3298 and #2509) + ## Rails 3.1.1 (October 07, 2011) ## * javascript_path and stylesheet_path now refer to /assets if asset pipelining @@ -488,6 +646,96 @@ * Add Rack::Cache to the default stack. Create a Rails store that delegates to the Rails cache, so by default, whatever caching layer you are using will be used for HTTP caching. Note that Rack::Cache will be used if you use #expires_in, #fresh_when or #stale with :public => true. Otherwise, the caching rules will apply to the browser only. *Yehuda Katz, Carl Lerche* +## Rails 3.0.12 (March 1, 2012) ## + +* Fix using `tranlate` helper with a html translation which uses the `:count` option for + pluralization. + + *Jon Leighton* + + +## Rails 3.0.11 (November 18, 2011) ## + +* Fix XSS security vulnerability in the `translate` helper method. When using interpolation + in combination with HTML-safe translations, the interpolated input would not get HTML + escaped. *GH 3664* + + Before: + + translate('foo_html', :something => '<script>') # => "...<script>..." + + After: + + translate('foo_html', :something => '<script>') # => "...<script>..." + + *Sergey Nartimov* + +* Implement a workaround for a bug in ruby-1.9.3p0 where an error would be + raised while attempting to convert a template from one encoding to another. + + Please see http://redmine.ruby-lang.org/issues/5564 for details of the bug. + + The workaround is to load all conversions into memory ahead of time, and will + only happen if the ruby version is exactly 1.9.3p0. The hope is obviously + that the underlying problem will be resolved in the next patchlevel release + of 1.9.3. + +* Fix assert_select_email to work on multipart and non-multipart emails as the method stopped working correctly in Rails 3.x due to changes in the new mail gem. + +* Fix url_for when passed a hash to prevent additional options (eg. :host, :protocol) from being added to the hash after calling it. + + +## Rails 3.0.10 (August 16, 2011) ## + +* Fixes an issue where cache sweepers with only after filters would have no + controller object, it would raise undefined method controller_name for nil [jeroenj] + +* Ensure status codes are logged when exceptions are raised. + +* Subclasses of OutputBuffer are respected. + +* Fixed ActionView::FormOptionsHelper#select with :multiple => false + +* Avoid extra call to Cache#read in case of a fragment cache hit + + +## Rails 3.0.9 (June 16, 2011) ## + +* json_escape will now return a SafeBuffer string if it receives SafeBuffer string [tenderlove] + +* Make sure escape_js returns SafeBuffer string if it receives SafeBuffer string [Prem Sichanugrist] + +* Fix text helpers to work correctly with the new SafeBuffer restriction [Paul Gallagher, Arun Agrawal, Prem Sichanugrist] + + +## Rails 3.0.8 (June 7, 2011) ## + +* It is prohibited to perform a in-place SafeBuffer mutation [tenderlove] + + The old behavior of SafeBuffer allowed you to mutate string in place via + method like `sub!`. These methods can add unsafe strings to a safe buffer, + and the safe buffer will continue to be marked as safe. + + An example problem would be something like this: + + <%= link_to('hello world', @user).sub!(/hello/, params[:xss]) %> + + In the above example, an untrusted string (`params[:xss]`) is added to the + safe buffer returned by `link_to`, and the untrusted content is successfully + sent to the client without being escaped. To prevent this from happening + `sub!` and other similar methods will now raise an exception when they are called on a safe buffer. + + In addition to the in-place versions, some of the versions of these methods which return a copy of the string will incorrectly mark strings as safe. For example: + + <%= link_to('hello world', @user).sub(/hello/, params[:xss]) %> + + The new versions will now ensure that *all* strings returned by these methods on safe buffers are marked unsafe. + + You can read more about this change in http://groups.google.com/group/rubyonrails-security/browse_thread/thread/2e516e7acc96c4fb + +* Fixed github issue #342 with asset paths and relative roots. + + ## Rails 3.0.7 (April 18, 2011) ## * No changes. diff --git a/actionpack/README.rdoc b/actionpack/README.rdoc index 076a93bbcd..1fdc57e14d 100644 --- a/actionpack/README.rdoc +++ b/actionpack/README.rdoc @@ -218,7 +218,7 @@ A short rundown of some of the major features: def show # the output of the method will be cached as - # ActionController::Base.page_cache_directory + "/weblog/show/n.html" + # ActionController::Base.page_cache_directory + "/weblog/show.html" # and the web server will pick it up without even hitting Rails end diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index b205acdb79..589a67dc02 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -18,12 +18,11 @@ Gem::Specification.new do |s| s.add_dependency('activesupport', version) s.add_dependency('activemodel', version) - s.add_dependency('rack-cache', '~> 1.1') + s.add_dependency('rack-cache', '~> 1.2') s.add_dependency('builder', '~> 3.0.0') s.add_dependency('rack', '~> 1.4.1') s.add_dependency('rack-test', '~> 0.6.1') s.add_dependency('journey', '~> 1.0.1') - s.add_dependency('sprockets', '~> 2.2.0') s.add_dependency('erubis', '~> 2.7.0') s.add_development_dependency('tzinfo', '~> 0.3.29') diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index fd6a46fbec..b068846a13 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,4 +1,5 @@ require 'erubis' +require 'set' require 'active_support/configurable' require 'active_support/descendants_tracker' require 'active_support/core_ext/module/anonymous' @@ -42,8 +43,8 @@ module AbstractController controller.public_instance_methods(true) end - # The list of hidden actions to an empty array. Defaults to an - # empty array. This can be modified by other modules or subclasses + # The list of hidden actions. Defaults to an empty array. + # This can be modified by other modules or subclasses # to specify particular actions as hidden. # # ==== Returns @@ -59,7 +60,7 @@ module AbstractController # itself. Finally, #hidden_actions are removed. # # ==== Returns - # * <tt>array</tt> - A list of all methods that should be considered actions. + # * <tt>set</tt> - A set of all methods that should be considered actions. def action_methods @action_methods ||= begin # All public instance methods of this class, including ancestors @@ -72,7 +73,7 @@ module AbstractController hidden_actions.to_a # Clear out AS callback method pollution - methods.reject { |method| method =~ /_one_time_conditions/ } + Set.new(methods.reject { |method| method =~ /_one_time_conditions/ }) end end @@ -85,7 +86,7 @@ module AbstractController # Returns the full controller name, underscored, without the ending Controller. # For instance, MyApp::MyPostsController would return "my_app/my_posts" for - # controller_name. + # controller_path. # # ==== Returns # * <tt>string</tt> diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index fffe3edac2..c0fa28cae9 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -8,7 +8,7 @@ module AbstractController include ActiveSupport::Callbacks included do - define_callbacks :process_action, :terminator => "response_body" + define_callbacks :process_action, :terminator => "response_body", :skip_after_callbacks_if_terminated => true end # Override AbstractController::Base's process_action to run the @@ -21,11 +21,9 @@ module AbstractController module ClassMethods # If :only or :except are used, convert the options into the - # primitive form (:per_key) used by ActiveSupport::Callbacks. + # :unless and :if options of ActiveSupport::Callbacks. # The basic idea is that :only => :index gets converted to - # :if => proc {|c| c.action_name == "index" }, but that the - # proc is only evaluated once per action for the lifetime of - # a Rails process. + # :if => proc {|c| c.action_name == "index" }. # # ==== Options # * <tt>only</tt> - The callback should be run only for this action @@ -33,11 +31,11 @@ module AbstractController def _normalize_callback_options(options) if only = options[:only] only = Array(only).map {|o| "action_name == '#{o}'"}.join(" || ") - options[:per_key] = {:if => only} + options[:if] = Array(options[:if]) << only end if except = options[:except] except = Array(except).map {|e| "action_name == '#{e}'"}.join(" || ") - options[:per_key] = {:unless => except} + options[:unless] = Array(options[:unless]) << except end end @@ -48,7 +46,7 @@ module AbstractController # callbacks. Note that skipping uses Ruby equality, so it's # impossible to skip a callback defined using an anonymous proc # using #skip_filter - def skip_filter(*names, &blk) + def skip_filter(*names) skip_before_filter(*names) skip_after_filter(*names) skip_around_filter(*names) @@ -66,7 +64,7 @@ module AbstractController # ==== Block Parameters # * <tt>name</tt> - The callback to be added # * <tt>options</tt> - A hash of options to be used when adding the callback - def _insert_callbacks(callbacks, block) + def _insert_callbacks(callbacks, block = nil) options = callbacks.last.is_a?(Hash) ? callbacks.pop : {} _normalize_callback_options(options) callbacks.push(block) if block @@ -92,7 +90,7 @@ module AbstractController ## # :method: skip_before_filter # - # :call-seq: skip_before_filter(names, block) + # :call-seq: skip_before_filter(names) # # Skip a before filter. See _insert_callbacks for parameter details. @@ -120,7 +118,7 @@ module AbstractController ## # :method: skip_after_filter # - # :call-seq: skip_after_filter(names, block) + # :call-seq: skip_after_filter(names) # # Skip an after filter. See _insert_callbacks for parameter details. @@ -148,7 +146,7 @@ module AbstractController ## # :method: skip_around_filter # - # :call-seq: skip_around_filter(names, block) + # :call-seq: skip_around_filter(names) # # Skip an around filter. See _insert_callbacks for parameter details. @@ -167,7 +165,6 @@ module AbstractController # for details on the allowed parameters. def #{filter}_filter(*names, &blk) # def before_filter(*names, &blk) _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options) end # end end # end @@ -176,15 +173,14 @@ module AbstractController # for details on the allowed parameters. def prepend_#{filter}_filter(*names, &blk) # def prepend_before_filter(*names, &blk) _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| - options[:if] = (Array(options[:if]) << "!halted") if #{filter == :after} # options[:if] = (Array(options[:if]) << "!halted") if false set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) end # end end # end # Skip a before, after or around filter. See _insert_callbacks # for details on the allowed parameters. - def skip_#{filter}_filter(*names, &blk) # def skip_before_filter(*names, &blk) - _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| + def skip_#{filter}_filter(*names) # def skip_before_filter(*names) + _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| skip_callback(:process_action, :#{filter}, name, options) # skip_callback(:process_action, :before, name, options) end # end end # end diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 6a6387632c..6f13ebe0d0 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -89,7 +89,7 @@ module AbstractController # class TillController < BankController # layout false # - # In these examples, we have three implicit lookup scenrios: + # In these examples, we have three implicit lookup scenarios: # * The BankController uses the "bank" layout. # * The ExchangeController uses the "exchange" layout. # * The CurrencyController inherits the layout from BankController. @@ -120,6 +120,7 @@ module AbstractController # def writers_and_readers # logged_in? ? "writer_layout" : "reader_layout" # end + # end # # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing # is logged in or not. @@ -127,7 +128,14 @@ module AbstractController # If you want to use an inline method, such as a proc, do something like this: # # class WeblogController < ActionController::Base - # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # layout proc { |controller| controller.logged_in? ? "writer_layout" : "reader_layout" } + # end + # + # If an argument isn't given to the proc, it's evaluated in the context of + # the current controller anyway. + # + # class WeblogController < ActionController::Base + # layout proc { logged_in? ? "writer_layout" : "reader_layout" } # end # # Of course, the most common way of specifying a layout is still just as a plain template name: @@ -136,8 +144,8 @@ module AbstractController # layout "weblog_standard" # end # - # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>. - # Otherwise, it will be looked up relative to the template root. + # The template will be looked always in <tt>app/views/layouts/</tt> folder. But you can point + # <tt>layouts</tt> folder direct also. <tt>layout "layouts/demo"</tt> is the same as <tt>layout "demo"</tt>. # # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists. # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent: @@ -238,8 +246,7 @@ module AbstractController # # If the specified layout is a: # String:: the String is the template name - # Symbol:: call the method specified by the symbol, which will return - # the template name + # Symbol:: call the method specified by the symbol, which will return the template name # false:: There is no layout # true:: raise an ArgumentError # @@ -280,6 +287,10 @@ module AbstractController <<-RUBY lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super RUBY + else + <<-RUBY + super + RUBY end layout_definition = case _layout @@ -295,12 +306,12 @@ module AbstractController end RUBY when Proc - define_method :_layout_from_proc, &_layout - "_layout_from_proc(self)" + define_method :_layout_from_proc, &_layout + _layout.arity == 0 ? "_layout_from_proc" : "_layout_from_proc(self)" when false nil when true - raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" + raise ArgumentError, "Layouts must be specified as a String, Symbol, Proc, false, or nil" when nil name_clause end @@ -322,7 +333,7 @@ module AbstractController super if _include_layout?(options) - layout = options.key?(:layout) ? options.delete(:layout) : :default + layout = options.delete(:layout) { :default } options[:layout] = _layout_for_option(layout) end end @@ -360,7 +371,7 @@ module AbstractController when false, nil then nil else raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" + "String, Proc, :default, true, or false, expected for `layout'; you passed #{name.inspect}" end end diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index ddc93464cd..7d73c6af8d 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -105,6 +105,7 @@ module AbstractController # Find and renders a template based on the options given. # :api: private def _render_template(options) #:nodoc: + lookup_context.rendered_format = nil if options[:formats] view_renderer.render(view_context, options) end diff --git a/actionpack/lib/abstract_controller/url_for.rb b/actionpack/lib/abstract_controller/url_for.rb index e4261068d8..4a95e1f276 100644 --- a/actionpack/lib/abstract_controller/url_for.rb +++ b/actionpack/lib/abstract_controller/url_for.rb @@ -1,10 +1,10 @@ -# Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class -# has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an -# exception will be raised. -# -# Note that this module is completely decoupled from HTTP - the only requirement is a valid -# <tt>_routes</tt> implementation. module AbstractController + # Includes +url_for+ into the host class (e.g. an abstract controller or mailer). The class + # has to provide a +RouteSet+ by implementing the <tt>_routes</tt> methods. Otherwise, an + # exception will be raised. + # + # Note that this module is completely decoupled from HTTP - the only requirement is a valid + # <tt>_routes</tt> implementation. module UrlFor extend ActiveSupport::Concern include ActionDispatch::Routing::UrlFor diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index a0c54dbd84..7c10fcbb8a 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -31,7 +31,6 @@ module ActionController autoload :RequestForgeryProtection autoload :Rescue autoload :Responder - autoload :SessionManagement autoload :Streaming autoload :Testing autoload :UrlFor diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3b82231b15..71425cd542 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -171,6 +171,16 @@ module ActionController class Base < Metal abstract! + # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument: + # + # class MetalController + # ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left| + # include left + # end + # end + # + # This gives better control over what you want to exclude and makes it easier + # to create a bare controller class, instead of listing the modules required manually. def self.without_modules(*modules) modules = modules.map do |m| m.is_a?(Symbol) ? ActionController.const_get(m) : m @@ -192,7 +202,6 @@ module ActionController Renderers::All, ConditionalGet, RackDelegation, - SessionManagement, Caching, MimeResponds, ImplicitRender, diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb index bd3b0b5df3..59ec197347 100644 --- a/actionpack/lib/action_controller/caching/actions.rb +++ b/actionpack/lib/action_controller/caching/actions.rb @@ -40,7 +40,7 @@ module ActionController #:nodoc: # # You can modify the default action cache path by passing a # <tt>:cache_path</tt> option. This will be passed directly to - # <tt>ActionCachePath.path_for</tt>. This is handy for actions with + # <tt>ActionCachePath.new</tt>. This is handy for actions with # multiple possible routes that should be cached differently. If a # block is given, it is called with the current controller instance. # @@ -103,8 +103,10 @@ module ActionController #:nodoc: end def _save_fragment(name, options) - content = response_body - content = content.join if content.is_a?(Array) + content = "" + response_body.each do |parts| + content << parts + end if caching_allowed? write_fragment(name, content, options) @@ -168,14 +170,14 @@ module ActionController #:nodoc: options.reverse_merge!(:format => @extension) if options.is_a?(Hash) end - path = controller.url_for(options).split(%r{://}).last + path = controller.url_for(options).split('://', 2).last @path = normalize!(path) end private def normalize!(path) path << 'index' if path[-1] == ?/ - path << ".#{extension}" if extension and !path.split('?').first.ends_with?(".#{extension}") + path << ".#{extension}" if extension and !path.split('?', 2).first.ends_with?(".#{extension}") URI.parser.unescape(path) end end diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb index 159f718029..307594d54a 100644 --- a/actionpack/lib/action_controller/caching/pages.rb +++ b/actionpack/lib/action_controller/caching/pages.rb @@ -99,7 +99,7 @@ module ActionController #:nodoc: # caches_page :index # # # cache the index action except for JSON requests - # caches_page :index, :if => Proc.new { |c| !c.request.format.json? } + # caches_page :index, :if => Proc.new { !request.format.json? } # # # don't gzip images # caches_page :image, :gzip => false diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb index 49cf70ec21..bb176ca3f9 100644 --- a/actionpack/lib/action_controller/caching/sweeping.rb +++ b/actionpack/lib/action_controller/caching/sweeping.rb @@ -54,6 +54,11 @@ module ActionController #:nodoc: class Sweeper < ActiveRecord::Observer #:nodoc: attr_accessor :controller + def initialize(*args) + super + @controller = nil + end + def before(controller) self.controller = controller callback(:before) if controller.perform_caching @@ -88,7 +93,7 @@ module ActionController #:nodoc: end def method_missing(method, *arguments, &block) - return unless @controller + super unless @controller @controller.__send__(method, *arguments, &block) end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 1645400693..5b25a0d303 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -111,15 +111,22 @@ module ActionController # Examples: # expires_in 20.minutes # expires_in 3.hours, :public => true - # expires_in 3.hours, 'max-stale' => 5.hours, :public => true + # expires_in 3.hours, :public => true, :must_revalidate => true # # This method will overwrite an existing Cache-Control header. # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + # + # The method will also ensure a HTTP Date header for client compatibility. def expires_in(seconds, options = {}) #:doc: - response.cache_control.merge!(:max_age => seconds, :public => options.delete(:public)) + response.cache_control.merge!( + :max_age => seconds, + :public => options.delete(:public), + :must_revalidate => options.delete(:must_revalidate) + ) options.delete(:private) response.cache_control[:extras] = options.map {|k,v| "#{k}=#{v}"} + response.date = Time.now unless response.date? end # Sets a HTTP 1.1 Cache-Control header of <tt>no-cache</tt> so no caching should occur by the browser or diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index b45f211e83..ac12cbb625 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -18,18 +18,33 @@ module ActionController # Force the request to this particular controller or specified actions to be # under HTTPS protocol. # - # Note that this method will not be effective on development environment. + # If you need to disable this for any reason (e.g. development) then you can use + # an +:if+ or +:unless+ condition. + # + # class AccountsController < ApplicationController + # force_ssl :if => :ssl_configured? + # + # def ssl_configured? + # !Rails.env.development? + # end + # end # # ==== Options + # * <tt>host</tt> - Redirect to a different host name # * <tt>only</tt> - The callback should be run only for this action # * <tt>except<tt> - The callback should be run for all actions except this action + # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a true value. + # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback + # will be called only when it returns a false value. def force_ssl(options = {}) host = options.delete(:host) before_filter(options) do - if !request.ssl? && !Rails.env.development? + unless request.ssl? redirect_options = {:protocol => 'https://', :status => :moved_permanently} redirect_options.merge!(:host => host) if host - flash.keep + redirect_options.merge!(:params => request.query_parameters) + flash.keep if respond_to?(:flash) redirect_to redirect_options end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d070eaae5d..1a4bca12d2 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -52,6 +52,7 @@ module ActionController module Helpers extend ActiveSupport::Concern + class << self; attr_accessor :helpers_path; end include AbstractController::Helpers included do diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 3d46163b74..44d2f740e6 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -263,7 +263,7 @@ module ActionController # The quality of the implementation depends on a good choice. # A nonce might, for example, be constructed as the base 64 encoding of # - # => time-stamp H(time-stamp ":" ETag ":" private-key) + # time-stamp H(time-stamp ":" ETag ":" private-key) # # where time-stamp is a server-generated time or other non-repeating value, # ETag is the value of the HTTP ETag header associated with the requested entity, @@ -279,7 +279,7 @@ module ActionController # # An implementation might choose not to accept a previously used nonce or a previously used digest, in order to # protect against a replay attack. Or, an implementation might choose to use one-time nonces or digests for - # POST or PUT requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 + # POST, PUT, or PATCH requests and a time-stamp for GET requests. For more details on the issues involved see Section 4 # of this document. # # The nonce is opaque to the client. Composed of Time, and hash of Time with secret @@ -293,7 +293,7 @@ module ActionController end # Might want a shorter timeout depending on whether the request - # is a PUT or POST, and if client is browser or web service. + # is a PATCH, PUT, or POST, and if client is browser or web service. # Can be much shorter if the Stale directive is implemented. This would # allow a user to use new nonce without prompting user again for their # username and password. diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index ca383be76b..fbb5d01e86 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -6,8 +6,6 @@ module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern - include ActionController::ImplicitRender - included do class_attribute :responder, :mimes_for_respond_to self.responder = ActionController::Responder @@ -58,7 +56,7 @@ module ActionController #:nodoc: # Clear all mime types in <tt>respond_to</tt>. # def clear_respond_to - self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze + self.mimes_for_respond_to = Hash.new.freeze end end @@ -191,25 +189,112 @@ module ActionController #:nodoc: def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? - if response = retrieve_response_from_mimes(mimes, &block) - response.call(nil) + if collector = retrieve_collector_from_mimes(mimes, &block) + response = collector.response + response ? response.call : render({}) end end - # respond_with wraps a resource around a responder for default representation. - # First it invokes respond_to, if a response cannot be found (ie. no block - # for the request was given and template was not available), it instantiates - # an ActionController::Responder with the controller and resource. + # For a given controller action, respond_with generates an appropriate + # response based on the mime-type requested by the client. # - # ==== Example + # If the method is called with just a resource, as in this example - # - # def index - # @users = User.all - # respond_with(@users) + # class PeopleController < ApplicationController + # respond_to :html, :xml, :json + # + # def index + # @people = Person.all + # respond_with @people + # end # end # - # It also accepts a block to be given. It's used to overwrite a default - # response: + # then the mime-type of the response is typically selected based on the + # request's Accept header and the set of available formats declared + # by previous calls to the controller's class method +respond_to+. Alternatively + # the mime-type can be selected by explicitly setting <tt>request.format</tt> in + # the controller. + # + # If an acceptable format is not identified, the application returns a + # '406 - not acceptable' status. Otherwise, the default response is to render + # a template named after the current action and the selected format, + # e.g. <tt>index.html.erb</tt>. If no template is available, the behavior + # depends on the selected format: + # + # * for an html response - if the request method is +get+, an exception + # is raised but for other requests such as +post+ the response + # depends on whether the resource has any validation errors (i.e. + # assuming that an attempt has been made to save the resource, + # e.g. by a +create+ action) - + # 1. If there are no errors, i.e. the resource + # was saved successfully, the response +redirect+'s to the resource + # 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+. + # Thus an example like this - + # + # respond_to :html, :xml + # + # def create + # @user = User.new(params[:user]) + # flash[:notice] = 'User was successfully created.' if @user.save + # respond_with(@user) + # end + # + # is equivalent, in the absence of <tt>create.html.erb</tt>, to - + # + # def create + # @user = User.new(params[:user]) + # respond_to do |format| + # if @user.save + # flash[:notice] = 'User was successfully created.' + # format.html { redirect_to(@user) } + # format.xml { render :xml => @user } + # else + # format.html { render :action => "new" } + # format.xml { render :xml => @user } + # end + # end + # end + # + # * for a javascript request - if the template isn't found, an exception is + # raised. + # * for other requests - i.e. data formats such as xml, json, csv etc, if + # the resource passed to +respond_with+ responds to <code>to_<format></code>, + # the method attempts to render the resource in the requested format + # directly, e.g. for an xml request, the response is equivalent to calling + # <code>render :xml => resource</code>. + # + # === Nested resources + # + # As outlined above, the +resources+ argument passed to +respond_with+ + # can play two roles. It can be used to generate the redirect url + # for successful html requests (e.g. for +create+ actions when + # no template exists), while for formats other than html and javascript + # it is the object that gets rendered, by being converted directly to the + # required format (again assuming no template exists). + # + # For redirecting successful html requests, +respond_with+ also supports + # the use of nested resources, which are supplied in the same way as + # in <code>form_for</code> and <code>polymorphic_url</code>. For example - + # + # def create + # @project = Project.find(params[:project_id]) + # @task = @project.comments.build(params[:task]) + # flash[:notice] = 'Task was successfully created.' if @task.save + # respond_with(@project, @task) + # end + # + # This would cause +respond_with+ to redirect to <code>project_task_url</code> + # instead of <code>task_url</code>. For request formats other than html or + # javascript, if multiple resources are passed in this way, it is the last + # one specified that is rendered. + # + # === Customizing response behavior + # + # Like +respond_to+, +respond_with+ may also be called with a block that + # can be used to overwrite any of the default responses, e.g. - # # def create # @user = User.new(params[:user]) @@ -220,21 +305,32 @@ module ActionController #:nodoc: # end # end # - # All options given to respond_with are sent to the underlying responder, - # except for the option :responder itself. Since the responder interface - # is quite simple (it just needs to respond to call), you can even give - # a proc to it. - # - # In order to use respond_with, first you need to declare the formats your - # controller responds to in the class level with a call to <tt>respond_to</tt>. + # The argument passed to the block is an ActionController::MimeResponds::Collector + # object which stores the responses for the formats defined within the + # block. Note that formats with responses defined explicitly in this way + # do not have to first be declared using the class method +respond_to+. + # + # Also, a hash passed to +respond_with+ immediately after the specified + # resource(s) is interpreted as a set of options relevant to all + # formats. Any option accepted by +render+ can be used, e.g. + # respond_with @people, :status => 200 + # However, note that these options are ignored after an unsuccessful attempt + # to save a resource, e.g. when automatically rendering <tt>:new</tt> + # after a post request. + # + # Two additional options are relevant specifically to +respond_with+ - + # 1. <tt>:location</tt> - overwrites the default redirect location used after + # a successful html +post+ request. + # 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 " << "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? - if response = retrieve_response_from_mimes(&block) + if collector = retrieve_collector_from_mimes(&block) options = resources.size == 1 ? {} : resources.extract_options! - options.merge!(:default_response => response) + options[:default_response] = collector.response (options.delete(:responder) || self.class.responder).call(self, resources, options) end end @@ -260,30 +356,59 @@ module ActionController #:nodoc: end end - # Collects mimes and return the response for the negotiated format. Returns - # nil if :not_acceptable was sent to the client. + # Returns a Collector object containing the appropriate mime-type response + # for the current request, based on the available responses defined by a block. + # In typical usage this is the block passed to +respond_with+ or +respond_to+. # - def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc: + # Sends :not_acceptable to the client and returns nil if no suitable format + # is available. + # + def retrieve_collector_from_mimes(mimes=nil, &block) #:nodoc: mimes ||= collect_mimes_from_class_level - collector = Collector.new(mimes) { |options| default_render(options || {}) } + collector = Collector.new(mimes) block.call(collector) if block_given? + format = collector.negotiate_format(request) - if format = request.negotiate_mime(collector.order) + if format self.content_type ||= format.to_s - lookup_context.freeze_formats([format.to_sym]) - collector.response_for(format) + lookup_context.formats = [format.to_sym] + lookup_context.rendered_format = lookup_context.formats.first + collector else head :not_acceptable nil end end - class Collector #:nodoc: + # A container for responses available from the current controller for + # requests for different mime-types sent to a particular action. + # + # The public controller methods +respond_with+ and +respond_to+ may be called + # with a block that is used to define responses to different mime-types, e.g. + # for +respond_to+ : + # + # respond_to do |format| + # format.html + # format.xml { render :xml => @people.to_xml } + # end + # + # In this usage, the argument passed to the block (+format+ above) is an + # instance of the ActionController::MimeResponds::Collector class. This + # object serves as a container in which available responses can be stored by + # calling any of the dynamically generated, mime-type-specific methods such + # as +html+, +xml+ etc on the Collector. Each response is represented by a + # corresponding block if present. + # + # A subsequent call to #negotiate_format(request) will enable the Collector + # to determine which specific mime-type it should respond with for the current + # request, with this response then being accessible by calling #response. + # + class Collector include AbstractController::Collector - attr_accessor :order + attr_accessor :order, :format - def initialize(mimes, &block) - @order, @responses, @default_response = [], {}, block + def initialize(mimes) + @order, @responses = [], {} mimes.each { |mime| send(mime) } end @@ -302,8 +427,12 @@ module ActionController #:nodoc: @responses[mime_type] ||= block end - def response_for(mime) - @responses[mime] || @responses[Mime::ALL] || @default_response + def response + @responses[format] || @responses[Mime::ALL] + end + + def negotiate_format(request) + @format = request.negotiate_mime(order) end end end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b07742e0e1..3ffb7ef426 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -93,7 +93,7 @@ module ActionController _compute_redirect_to_location options.call else url_for(options) - end.gsub(/[\r\n]/, '') + end.gsub(/[\0\r\n]/, '') end end end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index a677cdf15d..c5e7d4e357 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -14,7 +14,7 @@ module ActionController def render(*args) #:nodoc: raise ::AbstractController::DoubleRenderError if response_body super - self.content_type ||= Mime[formats.first].to_s + self.content_type ||= Mime[lookup_context.rendered_format].to_s response_body end diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index afa9243f02..3081c14c09 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -37,6 +37,10 @@ module ActionController #:nodoc: config_accessor :request_forgery_protection_token self.request_forgery_protection_token ||= :authenticity_token + # Controls how unverified request will be handled + config_accessor :request_forgery_protection_method + self.request_forgery_protection_method ||= :reset_session + # Controls whether request forgery protection is turned on or not. Turned off by default only in test mode. config_accessor :allow_forgery_protection self.allow_forgery_protection = true if allow_forgery_protection.nil? @@ -64,8 +68,10 @@ module ActionController #:nodoc: # Valid Options: # # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. + # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default). def protect_from_forgery(options = {}) self.request_forgery_protection_token ||= :authenticity_token + self.request_forgery_protection_method = options.delete(:with) if options.key?(:with) prepend_before_filter :verify_authenticity_token, options end end @@ -80,9 +86,19 @@ module ActionController #:nodoc: end # This is the method that defines the application behavior when a request is found to be unverified. - # By default, \Rails resets the session when it finds an unverified request. + # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request: + # + # * <tt>:reset_session</tt> - Resets the session. + # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception. def handle_unverified_request - reset_session + case request_forgery_protection_method + when :exception + raise ActionController::InvalidAuthenticityToken + when :reset_session + reset_session + else + raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session' + end end # Returns true or false if a request is verified. Checks: diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 9500a349cb..1e8990495c 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -53,7 +53,7 @@ module ActionController #:nodoc: # end # end # - # The same happens for PUT and DELETE requests. + # The same happens for PATCH/PUT and DELETE requests. # # === Nested resources # @@ -116,8 +116,9 @@ module ActionController #:nodoc: class Responder attr_reader :controller, :request, :format, :resource, :resources, :options - ACTIONS_FOR_VERBS = { + DEFAULT_ACTIONS_FOR_VERBS = { :post => :new, + :patch => :edit, :put => :edit } @@ -133,7 +134,7 @@ module ActionController #:nodoc: end delegate :head, :render, :redirect_to, :to => :controller - delegate :get?, :post?, :put?, :delete?, :to => :request + delegate :get?, :post?, :patch?, :put?, :delete?, :to => :request # Undefine :to_json and :to_yaml since it's defined on Object undef_method(:to_json) if method_defined?(:to_json) @@ -172,7 +173,7 @@ module ActionController #:nodoc: # responds to :to_format and display it. # def to_format - if get? || !has_errors? + if get? || !has_errors? || response_overridden? default_render else display_errors @@ -226,7 +227,11 @@ module ActionController #:nodoc: # controller. # def default_render - @default_response.call(options) + if @default_response + @default_response.call(options) + else + controller.default_render(options) + end end # Display is just a shortcut to render a resource with the current format. @@ -260,19 +265,23 @@ module ActionController #:nodoc: resource.respond_to?(:errors) && !resource.errors.empty? end - # By default, render the <code>:edit</code> action for HTML requests with failure, unless - # the verb is POST. + # By default, render the <code>:edit</code> action for HTML requests with errors, unless + # the verb was POST. # def default_action - @action ||= ACTIONS_FOR_VERBS[request.request_method_symbol] + @action ||= DEFAULT_ACTIONS_FOR_VERBS[request.request_method_symbol] end def resource_errors - respond_to?("#{format}_resource_errors") ? send("#{format}_resource_errors") : resource.errors + respond_to?("#{format}_resource_errors", true) ? send("#{format}_resource_errors") : resource.errors end def json_resource_errors {:errors => resource.errors} end + + def response_overridden? + @default_response.present? + end end end diff --git a/actionpack/lib/action_controller/metal/session_management.rb b/actionpack/lib/action_controller/metal/session_management.rb deleted file mode 100644 index 91d89ff9a4..0000000000 --- a/actionpack/lib/action_controller/metal/session_management.rb +++ /dev/null @@ -1,9 +0,0 @@ -module ActionController #:nodoc: - module SessionManagement #:nodoc: - extend ActiveSupport::Concern - - module ClassMethods - - end - end -end diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb index 0b40b1fc4c..8e7b56dbcc 100644 --- a/actionpack/lib/action_controller/metal/url_for.rb +++ b/actionpack/lib/action_controller/metal/url_for.rb @@ -1,25 +1,25 @@ -# Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing -# the <tt>_routes</tt> method. Otherwise, an exception will be raised. -# -# In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define -# url options like the +host+. In order to do so, this module requires the host class -# to implement +env+ and +request+, which need to be a Rack-compatible. -# -# Example: -# -# class RootUrl -# include ActionController::UrlFor -# include Rails.application.routes.url_helpers -# -# delegate :env, :request, :to => :controller -# -# def initialize(controller) -# @controller = controller -# @url = root_path # named route from the application. -# end -# end -# module ActionController + # Includes +url_for+ into the host class. The class has to provide a +RouteSet+ by implementing + # the <tt>_routes</tt> method. Otherwise, an exception will be raised. + # + # In addition to <tt>AbstractController::UrlFor</tt>, this module accesses the HTTP layer to define + # url options like the +host+. In order to do so, this module requires the host class + # to implement +env+ and +request+, which need to be a Rack-compatible. + # + # Example: + # + # class RootUrl + # include ActionController::UrlFor + # include Rails.application.routes.url_helpers + # + # delegate :env, :request, :to => :controller + # + # def initialize(controller) + # @controller = controller + # @url = root_path # named route from the application. + # end + # end + # module UrlFor extend ActiveSupport::Concern @@ -42,6 +42,5 @@ module ActionController @_url_options end end - end end diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index a288e69649..851a2c4aee 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -3,33 +3,32 @@ require "action_controller" require "action_dispatch/railtie" require "action_view/railtie" require "abstract_controller/railties/routes_helpers" -require "action_controller/railties/paths" +require "action_controller/railties/helpers" module ActionController - class Railtie < Rails::Railtie + class Railtie < Rails::Railtie #:nodoc: config.action_controller = ActiveSupport::OrderedOptions.new - initializer "action_controller.logger" do - ActiveSupport.on_load(:action_controller) { self.logger ||= Rails.logger } - end - - initializer "action_controller.initialize_framework_caches" do - ActiveSupport.on_load(:action_controller) { self.cache_store ||= Rails.cache } - end - initializer "action_controller.assets_config", :group => :all do |app| app.config.action_controller.assets_dir ||= app.config.paths["public"].first end + initializer "action_controller.set_helpers_path" do |app| + ActionController::Helpers.helpers_path = app.helpers_paths + end + initializer "action_controller.set_configs" do |app| paths = app.config.paths options = app.config.action_controller + options.logger ||= Rails.logger + options.cache_store ||= Rails.cache + options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first options.page_cache_directory ||= paths["public"].first - # make sure readers methods get compiled + # Ensure readers methods get compiled options.asset_path ||= app.config.asset_path options.asset_host ||= app.config.asset_host options.relative_url_root ||= app.config.relative_url_root @@ -37,8 +36,16 @@ module ActionController ActiveSupport.on_load(:action_controller) do include app.routes.mounted_helpers extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) - extend ::ActionController::Railties::Paths.with(app) - options.each { |k,v| send("#{k}=", v) } + extend ::ActionController::Railties::Helpers + + options.each do |k,v| + k = "#{k}=" + if respond_to?(k) + send(k, v) + elsif !Base.respond_to?(k) + raise "Invalid option key: #{k}" + end + end end end diff --git a/actionpack/lib/action_controller/railties/helpers.rb b/actionpack/lib/action_controller/railties/helpers.rb new file mode 100644 index 0000000000..3985c6b273 --- /dev/null +++ b/actionpack/lib/action_controller/railties/helpers.rb @@ -0,0 +1,22 @@ +module ActionController + module Railties + module Helpers + def inherited(klass) + super + return unless klass.respond_to?(:helpers_path=) + + if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } + paths = namespace.railtie_helpers_paths + else + paths = ActionController::Helpers.helpers_path + end + + klass.helpers_path = paths + + if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers + klass.helper :all + end + end + end + end +end diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb deleted file mode 100644 index bbe63149ad..0000000000 --- a/actionpack/lib/action_controller/railties/paths.rb +++ /dev/null @@ -1,25 +0,0 @@ -module ActionController - module Railties - module Paths - def self.with(app) - Module.new do - define_method(:inherited) do |klass| - super(klass) - - if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_helpers_paths) } - paths = namespace.railtie_helpers_paths - else - paths = app.helpers_paths - end - - klass.helpers_path = paths - - if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers - klass.helper :all - end - end - end - end - end - end -end diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb index 9c38ff44d8..18dda978b3 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -2,8 +2,8 @@ require 'active_support/core_ext/module' module ActionController # The record identifier encapsulates a number of naming conventions for dealing with records, like Active Records or - # Active Resources or pretty much any other model type that has an id. These patterns are then used to try elevate - # the view actions to a higher logical level. Example: + # pretty much any other model type that has an id. These patterns are then used to try elevate the view actions to + # a higher logical level. Example: # # # routes # resources :posts diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 1e226fc336..9bd2e622ad 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -56,6 +56,9 @@ module ActionController # # assert that the "new" view template was rendered # assert_template "new" # + # # assert that the exact template "admin/posts/new" was rendered + # assert_template %r{\Aadmin/posts/new\Z} + # # # assert that the "_customer" partial was rendered twice # assert_template :partial => '_customer', :count => 2 # @@ -69,12 +72,16 @@ module ActionController # assert_template :partial => '_customer', :locals => { :customer => @customer } # def assert_template(options = {}, message = nil) + # Force body to be read in case the + # template is being streamed + response.body + case options - when NilClass, String, Symbol + when NilClass, String, Symbol, Regexp options = options.to_s if Symbol === options rendered = @templates msg = message || sprintf("expecting <%s> but rendering with <%s>", - options, rendered.keys) + options.inspect, rendered.keys) assert_block(msg) do if options rendered.any? { |t,num| t.match(options) } @@ -117,6 +124,8 @@ module ActionController assert @partials.empty?, "Expected no partials to be rendered" end + else + raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil" end end end @@ -225,7 +234,7 @@ module ActionController # == Basic example # # Functional tests are written as follows: - # 1. First, one uses the +get+, +post+, +put+, +delete+ or +head+ method to simulate + # 1. First, one uses the +get+, +post+, +patch+, +put+, +delete+ or +head+ method to simulate # an HTTP request. # 2. Then, one asserts whether the current state is as expected. "State" can be anything: # the controller's HTTP response, the database contents, etc. @@ -392,6 +401,11 @@ module ActionController process(action, "POST", *args) end + # Executes a request simulating PATCH HTTP method and set/volley the response + def patch(action, *args) + process(action, "PATCH", *args) + end + # Executes a request simulating PUT HTTP method and set/volley the response def put(action, *args) process(action, "PUT", *args) @@ -465,7 +479,6 @@ module ActionController @request.session["flash"].sweep @controller.request = @request - @controller.params.merge!(parameters) build_request_uri(action, parameters) @controller.class.class_eval { include Testing } @controller.recycle! @@ -491,11 +504,6 @@ module ActionController end end - # Cause the action to be rescued according to the regular rules for rescue_action when the visitor is not local - def rescue_action_in_public! - @request.remote_addr = '208.77.188.166' # example.com - end - included do include ActionController::TemplateAssertions include ActionDispatch::Assertions diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb index 24ffc28710..e9b50ff8ce 100644 --- a/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +++ b/actionpack/lib/action_controller/vendor/html-scanner/html/sanitizer.rb @@ -5,6 +5,7 @@ require 'active_support/core_ext/class/attribute' module HTML class Sanitizer def sanitize(text, options = {}) + validate_options(options) return text unless sanitizeable?(text) tokenize(text, options).join end @@ -27,6 +28,16 @@ module HTML def process_node(node, result, options) result << node.to_s end + + def validate_options(options) + if options[:tags] && !options[:tags].is_a?(Enumerable) + raise ArgumentError, "You should pass :tags as an Enumerable" + end + + if options[:attributes] && !options[:attributes].is_a?(Enumerable) + raise ArgumentError, "You should pass :attributes as an Enumerable" + end + end end class FullSanitizer < Sanitizer diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index a9542a7d1b..e3b04ac097 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -61,6 +61,7 @@ module ActionDispatch autoload :Reloader autoload :RemoteIp autoload :ShowExceptions + autoload :SSL autoload :Static end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index bea62b94d2..5ee4c044ea 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -60,6 +60,20 @@ module ActionDispatch headers[LAST_MODIFIED] = utc_time.httpdate end + def date + if date_header = headers['Date'] + Time.httpdate(date_header) + end + end + + def date? + headers.include?('Date') + end + + def date=(utc_time) + headers['Date'] = utc_time.httpdate + end + def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}") diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 02a15ad599..132b0c82bc 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -50,7 +50,7 @@ module ActionDispatch end def env_filter - parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/) + parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"]) end def parameter_filter_for(filters) diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index 25affb9f50..e039eb1288 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -82,6 +82,7 @@ module Mime class << self TRAILING_STAR_REGEXP = /(text|application)\/\*/ + PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/ def lookup(string) LOOKUP[string] @@ -108,6 +109,7 @@ module Mime def parse(accept_header) if accept_header !~ /,/ + accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first if accept_header =~ TRAILING_STAR_REGEXP parse_data_with_trailing_star($1) else @@ -117,7 +119,7 @@ module Mime # keep track of creation order to keep the subsequent sort stable list, index = [], 0 accept_header.split(/,/).each do |header| - params, q = header.split(/;\s*q=/) + params, q = header.split(PARAMETER_SEPARATOR_REGEXP) if params.present? params.strip! diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 0a0ebe7fad..796e0dbc45 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -17,7 +17,8 @@ module ActionDispatch include ActionDispatch::Http::Upload include ActionDispatch::Http::URL - LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze + LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/] + ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR @@ -97,6 +98,12 @@ module ActionDispatch HTTP_METHOD_LOOKUP[request_method] == :post end + # Is this a PATCH request? + # Equivalent to <tt>request.request_method == :patch</tt>. + def patch? + HTTP_METHOD_LOOKUP[request_method] == :patch + end + # Is this a PUT request? # Equivalent to <tt>request.request_method_symbol == :put</tt>. def put? @@ -244,7 +251,7 @@ module ActionDispatch # True if the request came from localhost, 127.0.0.1. def local? - LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip } + LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip end private diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 84732085f0..078229efd2 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -51,12 +51,13 @@ module ActionDispatch # :nodoc: # If a character set has been defined for this response (see charset=) then # the character set information will also be included in the content type # information. - attr_accessor :charset, :content_type + attr_accessor :charset + attr_reader :content_type CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze - + cattr_accessor(:default_charset) { "utf-8" } include Rack::Response::Helpers @@ -83,6 +84,10 @@ module ActionDispatch # :nodoc: @status = Rack::Utils.status_code(status) end + def content_type=(content_type) + @content_type = content_type.to_s + end + # The response code of the request def response_code @status diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb index 5ab99d1061..ce8c2729e9 100644 --- a/actionpack/lib/action_dispatch/http/upload.rb +++ b/actionpack/lib/action_dispatch/http/upload.rb @@ -4,11 +4,12 @@ module ActionDispatch attr_accessor :original_filename, :content_type, :tempfile, :headers def initialize(hash) + @tempfile = hash[:tempfile] + raise(ArgumentError, ':tempfile is required') unless @tempfile + @original_filename = encode_filename(hash[:filename]) @content_type = hash[:type] @headers = hash[:head] - @tempfile = hash[:tempfile] - raise(ArgumentError, ':tempfile is required') unless @tempfile end def read(*args) @@ -16,18 +17,15 @@ module ActionDispatch end # Delegate these methods to the tempfile. - [:open, :path, :rewind, :size].each do |method| + [:open, :path, :rewind, :size, :eof?].each do |method| class_eval "def #{method}; @tempfile.#{method}; end" end - + private + def encode_filename(filename) # Encode the filename in the utf8 encoding, unless it is nil - if filename - filename.force_encoding("UTF-8").encode! - else - filename - end + filename.force_encoding("UTF-8").encode! if filename end end diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 80ffbe575b..f9dae5dad7 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -40,7 +40,9 @@ module ActionDispatch rewritten_url << ":#{options.delete(:port)}" if options[:port] end - path = options.delete(:path) || '' + path = "" + path << options.delete(:script_name).to_s.chomp("/") + path << options.delete(:path).to_s params = options[:params] || {} params.reject! {|k,v| v.to_param.nil? } diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 8c0f4052ec..338b116940 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -5,7 +5,7 @@ module ActionDispatch class Callbacks include ActiveSupport::Callbacks - define_callbacks :call, :rescuable => true + define_callbacks :call class << self delegate :to_prepare, :to_cleanup, :to => "ActionDispatch::Reloader" @@ -24,9 +24,15 @@ module ActionDispatch end def call(env) - run_callbacks :call do - @app.call(env) + error = nil + result = run_callbacks :call do + begin + @app.call(env) + rescue => error + end end + raise error if error + result end end end diff --git a/actionpack/lib/action_dispatch/middleware/remote_ip.rb b/actionpack/lib/action_dispatch/middleware/remote_ip.rb index 030ccb2017..d924f21fad 100644 --- a/actionpack/lib/action_dispatch/middleware/remote_ip.rb +++ b/actionpack/lib/action_dispatch/middleware/remote_ip.rb @@ -18,11 +18,13 @@ module ActionDispatch def initialize(app, check_ip_spoofing = true, custom_proxies = nil) @app = app @check_ip = check_ip_spoofing - if custom_proxies - custom_regexp = Regexp.new(custom_proxies) - @proxies = Regexp.union(TRUSTED_PROXIES, custom_regexp) + @proxies = case custom_proxies + when Regexp + custom_proxies + when nil + TRUSTED_PROXIES else - @proxies = TRUSTED_PROXIES + Regexp.union(TRUSTED_PROXIES, custom_proxies) end end @@ -57,7 +59,7 @@ module ActionDispatch "HTTP_X_FORWARDED_FOR=#{@env['HTTP_X_FORWARDED_FOR'].inspect}" end - not_proxy = client_ip || forwarded_ips.last || remote_addrs.first + not_proxy = client_ip || forwarded_ips.first || remote_addrs.first # Return first REMOTE_ADDR if there are no other options not_proxy || ips_from('REMOTE_ADDR', :allow_proxies).first diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb index d5a0b80fd5..6fff94707c 100644 --- a/actionpack/lib/action_dispatch/middleware/request_id.rb +++ b/actionpack/lib/action_dispatch/middleware/request_id.rb @@ -19,10 +19,7 @@ module ActionDispatch def call(env) env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id - status, headers, body = @app.call(env) - - headers["X-Request-Id"] = env["action_dispatch.request_id"] - [ status, headers, body ] + @app.call(env).tap { |status, headers, body| headers["X-Request-Id"] = env["action_dispatch.request_id"] } end private diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb index d3b6fd12fa..1db6194271 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb @@ -1,5 +1,4 @@ require 'action_dispatch/middleware/session/abstract_store' -require 'rack/session/memcache' module ActionDispatch module Session diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index 8ebf870b95..a4866f5a8f 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -59,7 +59,8 @@ module ActionDispatch end def set_session(env, sid, session_data, options) - session_data.merge("session_id" => sid) + session_data["session_id"] = sid + session_data end def set_cookie(env, session_id, cookie) diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 836136eb95..ab740a0190 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -9,7 +9,7 @@ module ActionDispatch # of ShowExceptions. Everytime there is an exception, ShowExceptions will # store the exception in env["action_dispatch.exception"], rewrite the # PATH_INFO to the exception status code and call the rack app. - # + # # If the application returns a "X-Cascade" pass response, this middleware # will send an empty response as result with the correct status code. # If any exception happens inside the exceptions app, this middleware diff --git a/actionpack/lib/action_dispatch/middleware/ssl.rb b/actionpack/lib/action_dispatch/middleware/ssl.rb new file mode 100644 index 0000000000..9098f4e170 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/ssl.rb @@ -0,0 +1,70 @@ +module ActionDispatch + class SSL + YEAR = 31536000 + + def self.default_hsts_options + { :expires => YEAR, :subdomains => false } + end + + def initialize(app, options = {}) + @app = app + + @hsts = options.fetch(:hsts, {}) + @hsts = {} if @hsts == true + @hsts = self.class.default_hsts_options.merge(@hsts) if @hsts + + @host = options[:host] + @port = options[:port] + end + + def call(env) + request = Request.new(env) + + if request.ssl? + status, headers, body = @app.call(env) + headers = hsts_headers.merge(headers) + flag_cookies_as_secure!(headers) + [status, headers, body] + else + redirect_to_https(request) + end + end + + private + def redirect_to_https(request) + url = URI(request.url) + url.scheme = "https" + url.host = @host if @host + url.port = @port if @port + headers = hsts_headers.merge('Content-Type' => 'text/html', + 'Location' => url.to_s) + + [301, headers, []] + end + + # http://tools.ietf.org/html/draft-hodges-strict-transport-sec-02 + def hsts_headers + if @hsts + value = "max-age=#{@hsts[:expires]}" + value += "; includeSubDomains" if @hsts[:subdomains] + { 'Strict-Transport-Security' => value } + else + {} + end + end + + def flag_cookies_as_secure!(headers) + if cookies = headers['Set-Cookie'] + cookies = cookies.split("\n") + + headers['Set-Cookie'] = cookies.map { |cookie| + if cookie !~ /;\s+secure(;|$)/ + "#{cookie}; secure" + else + cookie + end + }.join("\n") + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index a4308f528c..28e8fbdab8 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -93,8 +93,9 @@ module ActionDispatch end def swap(target, *args, &block) - insert_before(target, *args, &block) - delete(target) + index = assert_index(target, :before) + insert(index, *args, &block) + middlewares.delete_at(index + 1) end def delete(target) diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index 404943d720..9073e6582d 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -1,4 +1,5 @@ require 'rack/utils' +require 'active_support/core_ext/uri' module ActionDispatch class FileHandler @@ -11,14 +12,14 @@ module ActionDispatch def match?(path) path = path.dup - full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path)) + full_path = path.empty? ? @root : File.join(@root, escape_glob_chars(unescape_path(path))) paths = "#{full_path}#{ext}" matches = Dir[paths] match = matches.detect { |m| File.file?(m) } if match match.sub!(@compiled_root, '') - match + ::Rack::Utils.escape(match) end end @@ -32,6 +33,15 @@ module ActionDispatch "{,#{ext},/index#{ext}}" end end + + def unescape_path(path) + URI.parser.unescape(path) + end + + def escape_glob_chars(path) + path.force_encoding('binary') if path.respond_to? :force_encoding + path.gsub(/[*?{}\[\]]/, "\\\\\\&") + end end class Static diff --git a/actionpack/lib/action_dispatch/railtie.rb b/actionpack/lib/action_dispatch/railtie.rb index 35f901c575..62f906219c 100644 --- a/actionpack/lib/action_dispatch/railtie.rb +++ b/actionpack/lib/action_dispatch/railtie.rb @@ -16,7 +16,7 @@ module ActionDispatch config.action_dispatch.rack_cache = { :metastore => "rails:/", :entitystore => "rails:/", - :verbose => true + :verbose => false } initializer "action_dispatch.configure" do |app| diff --git a/actionpack/lib/action_dispatch/routing.rb b/actionpack/lib/action_dispatch/routing.rb index 107fe80d1f..38a0270151 100644 --- a/actionpack/lib/action_dispatch/routing.rb +++ b/actionpack/lib/action_dispatch/routing.rb @@ -182,10 +182,13 @@ module ActionDispatch # # == HTTP Methods # - # Using the <tt>:via</tt> option when specifying a route allows you to restrict it to a specific HTTP method. - # Possible values are <tt>:post</tt>, <tt>:get</tt>, <tt>:put</tt>, <tt>:delete</tt> and <tt>:any</tt>. - # If your route needs to respond to more than one method you can use an array, e.g. <tt>[ :get, :post ]</tt>. - # The default value is <tt>:any</tt> which means that the route will respond to any of the HTTP methods. + # Using the <tt>:via</tt> option when specifying a route allows you to + # restrict it to a specific HTTP method. Possible values are <tt>:post</tt>, + # <tt>:get</tt>, <tt>:patch</tt>, <tt>:put</tt>, <tt>:delete</tt> and + # <tt>:any</tt>. If your route needs to respond to more than one method you + # can use an array, e.g. <tt>[ :get, :post ]</tt>. The default value is + # <tt>:any</tt> which means that the route will respond to any of the HTTP + # methods. # # Examples: # @@ -198,7 +201,7 @@ module ActionDispatch # === HTTP helper methods # # An alternative method of specifying which HTTP method a route should respond to is to use the helper - # methods <tt>get</tt>, <tt>post</tt>, <tt>put</tt> and <tt>delete</tt>. + # methods <tt>get</tt>, <tt>post</tt>, <tt>patch</tt>, <tt>put</tt> and <tt>delete</tt>. # # Examples: # @@ -283,6 +286,6 @@ module ActionDispatch autoload :PolymorphicRoutes, 'action_dispatch/routing/polymorphic_routes' SEPARATORS = %w( / . ? ) #:nodoc: - HTTP_METHODS = [:get, :head, :post, :put, :delete, :options] #:nodoc: + HTTP_METHODS = [:get, :head, :post, :patch, :put, :delete, :options] #:nodoc: end end diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index a17a39bed3..fccbff1749 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1,8 +1,7 @@ require 'active_support/core_ext/hash/except' require 'active_support/core_ext/object/blank' -require 'active_support/core_ext/object/inclusion' +require 'active_support/core_ext/enumerable' require 'active_support/inflector' -require 'active_support/deprecation' require 'action_dispatch/routing/redirection' module ActionDispatch @@ -255,10 +254,15 @@ module ActionDispatch # # For options, see +match+, as +root+ uses it internally. # + # You can also pass a string which will expand + # + # root 'pages#main' + # # You should put the root route at the top of <tt>config/routes.rb</tt>, # because this means it will be matched first. As this is the most popular route # of most Rails applications, this is beneficial. def root(options = {}) + options = { :to => options } if options.is_a?(String) match '/', { :as => :root }.merge(options) end @@ -329,7 +333,7 @@ module ActionDispatch # +call+ or a string representing a controller's action. # # match 'path', :to => 'controller#action' - # match 'path', :to => lambda { [200, {}, "Success!"] } + # match 'path', :to => lambda { |env| [200, {}, "Success!"] } # match 'path', :to => RackApp # # [:on] @@ -447,7 +451,11 @@ module ActionDispatch _route = @set.named_routes.routes[name.to_sym] _routes = @set app.routes.define_mounted_helper(name) - app.routes.class_eval do + app.routes.singleton_class.class_eval do + define_method :mounted? do + true + end + define_method :_generate_prefix do |options| prefix_options = options.slice(*_route.segment_keys) # we must actually delete prefix segment keys to avoid passing them to next url_for @@ -466,7 +474,7 @@ module ActionDispatch # # Example: # - # get 'bacon', :to => 'food#bacon' + # get 'bacon', :to => 'food#bacon' def get(*args, &block) map_method(:get, args, &block) end @@ -476,17 +484,27 @@ module ActionDispatch # # Example: # - # post 'bacon', :to => 'food#bacon' + # post 'bacon', :to => 'food#bacon' def post(*args, &block) map_method(:post, args, &block) end + # Define a route that only recognizes HTTP PATCH. + # For supported arguments, see <tt>Base#match</tt>. + # + # Example: + # + # patch 'bacon', :to => 'food#bacon' + def patch(*args, &block) + map_method(:patch, args, &block) + end + # Define a route that only recognizes HTTP PUT. # For supported arguments, see <tt>Base#match</tt>. # # Example: # - # put 'bacon', :to => 'food#bacon' + # put 'bacon', :to => 'food#bacon' def put(*args, &block) map_method(:put, args, &block) end @@ -496,23 +514,13 @@ module ActionDispatch # # Example: # - # delete 'broccoli', :to => 'food#broccoli' + # delete 'broccoli', :to => 'food#broccoli' def delete(*args, &block) map_method(:delete, args, &block) end private def map_method(method, args, &block) - if args.length > 2 - ActiveSupport::Deprecation.warn <<-eowarn -The method signature of #{method}() is changing to: - - #{method}(path, options = {}, &block) - -Calling with multiple paths is deprecated. - eowarn - end - options = args.extract_options! options[:via] = method match(*args, options, &block) @@ -533,13 +541,13 @@ Calling with multiple paths is deprecated. # This will create a number of routes for each of the posts and comments # controller. For <tt>Admin::PostsController</tt>, Rails will create: # - # GET /admin/posts - # GET /admin/posts/new - # POST /admin/posts - # GET /admin/posts/1 - # GET /admin/posts/1/edit - # PUT /admin/posts/1 - # DELETE /admin/posts/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 # # If you want to route /posts (without the prefix /admin) to # <tt>Admin::PostsController</tt>, you could use @@ -567,13 +575,13 @@ Calling with multiple paths is deprecated. # not use scope. In the last case, the following paths map to # +PostsController+: # - # GET /admin/posts - # GET /admin/posts/new - # POST /admin/posts - # GET /admin/posts/1 - # GET /admin/posts/1/edit - # PUT /admin/posts/1 - # DELETE /admin/posts/1 + # GET /admin/posts + # GET /admin/posts/new + # POST /admin/posts + # GET /admin/posts/1 + # GET /admin/posts/1/edit + # PATCH/PUT /admin/posts/1 + # DELETE /admin/posts/1 module Scoping # Scopes a set of routes to the given default options. # @@ -662,13 +670,13 @@ Calling with multiple paths is deprecated. # # This generates the following routes: # - # admin_posts GET /admin/posts(.:format) admin/posts#index - # admin_posts POST /admin/posts(.:format) admin/posts#create - # new_admin_post GET /admin/posts/new(.:format) admin/posts#new - # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit - # admin_post GET /admin/posts/:id(.:format) admin/posts#show - # admin_post PUT /admin/posts/:id(.:format) admin/posts#update - # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy + # admin_posts GET /admin/posts(.:format) admin/posts#index + # admin_posts POST /admin/posts(.:format) admin/posts#create + # new_admin_post GET /admin/posts/new(.:format) admin/posts#new + # edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit + # admin_post GET /admin/posts/:id(.:format) admin/posts#show + # admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update + # admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy # # === Options # @@ -872,17 +880,18 @@ Calling with multiple paths is deprecated. # CANONICAL_ACTIONS holds all actions that does not need a prefix or # a path appended since they fit properly in their scope level. VALID_ON_OPTIONS = [:new, :collection, :member] - RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except] + RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param] CANONICAL_ACTIONS = %w(index create new show update destroy) class Resource #:nodoc: - attr_reader :controller, :path, :options + attr_reader :controller, :path, :options, :param def initialize(entities, options = {}) @name = entities.to_s @path = (options[:path] || @name).to_s @controller = (options[:controller] || @name).to_s @as = options[:as] + @param = options[:param] || :id @options = options end @@ -927,7 +936,7 @@ Calling with multiple paths is deprecated. alias :collection_scope :path def member_scope - "#{path}/:id" + "#{path}/:#{param}" end def new_scope(new_path) @@ -935,7 +944,7 @@ Calling with multiple paths is deprecated. end def nested_scope - "#{path}/:#{singular}_id" + "#{path}/:#{singular}_#{param}" end end @@ -983,12 +992,12 @@ Calling with multiple paths is deprecated. # the +GeoCoders+ controller (note that the controller is named after # the plural): # - # GET /geocoder/new - # POST /geocoder - # GET /geocoder - # GET /geocoder/edit - # PUT /geocoder - # DELETE /geocoder + # GET /geocoder/new + # POST /geocoder + # GET /geocoder + # GET /geocoder/edit + # PATCH/PUT /geocoder + # DELETE /geocoder # # === Options # Takes same options as +resources+. @@ -1011,9 +1020,12 @@ Calling with multiple paths is deprecated. end if parent_resource.actions.include?(:new) member do - get :edit if parent_resource.actions.include?(:edit) - get :show if parent_resource.actions.include?(:show) - put :update if parent_resource.actions.include?(:update) + get :edit if parent_resource.actions.include?(:edit) + get :show if parent_resource.actions.include?(:show) + if parent_resource.actions.include?(:update) + patch :update + put :update + end delete :destroy if parent_resource.actions.include?(:destroy) end end @@ -1031,13 +1043,13 @@ Calling with multiple paths is deprecated. # creates seven different routes in your application, all mapping to # the +Photos+ controller: # - # GET /photos - # GET /photos/new - # POST /photos - # GET /photos/:id - # GET /photos/:id/edit - # PUT /photos/:id - # DELETE /photos/:id + # GET /photos + # GET /photos/new + # POST /photos + # GET /photos/:id + # GET /photos/:id/edit + # PATCH/PUT /photos/:id + # DELETE /photos/:id # # Resources can also be nested infinitely by using this block syntax: # @@ -1047,13 +1059,13 @@ Calling with multiple paths is deprecated. # # This generates the following comments routes: # - # GET /photos/:photo_id/comments - # GET /photos/:photo_id/comments/new - # POST /photos/:photo_id/comments - # GET /photos/:photo_id/comments/:id - # GET /photos/:photo_id/comments/:id/edit - # PUT /photos/:photo_id/comments/:id - # DELETE /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments + # GET /photos/:photo_id/comments/new + # POST /photos/:photo_id/comments + # GET /photos/:photo_id/comments/:id + # GET /photos/:photo_id/comments/:id/edit + # PATCH/PUT /photos/:photo_id/comments/:id + # DELETE /photos/:photo_id/comments/:id # # === Options # Takes same options as <tt>Base#match</tt> as well as: @@ -1115,13 +1127,32 @@ Calling with multiple paths is deprecated. # # The +comments+ resource here will have the following routes generated for it: # - # post_comments GET /posts/:post_id/comments(.:format) - # post_comments POST /posts/:post_id/comments(.:format) - # new_post_comment GET /posts/:post_id/comments/new(.:format) - # edit_comment GET /sekret/comments/:id/edit(.:format) - # comment GET /sekret/comments/:id(.:format) - # comment PUT /sekret/comments/:id(.:format) - # comment DELETE /sekret/comments/:id(.:format) + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_comment GET /sekret/comments/:id/edit(.:format) + # comment GET /sekret/comments/:id(.:format) + # comment PATCH/PUT /sekret/comments/:id(.:format) + # comment DELETE /sekret/comments/:id(.:format) + # + # [:shallow_prefix] + # Prefixes nested shallow route names with specified prefix. + # + # scope :shallow_prefix => "sekret" do + # resources :posts do + # resources :comments, :shallow => true + # end + # end + # + # The +comments+ resource here will have the following routes generated for it: + # + # post_comments GET /posts/:post_id/comments(.:format) + # post_comments POST /posts/:post_id/comments(.:format) + # new_post_comment GET /posts/:post_id/comments/new(.:format) + # edit_sekret_comment GET /comments/:id/edit(.:format) + # sekret_comment GET /comments/:id(.:format) + # sekret_comment PATCH/PUT /comments/:id(.:format) + # sekret_comment DELETE /comments/:id(.:format) # # === Examples # @@ -1150,9 +1181,12 @@ Calling with multiple paths is deprecated. end if parent_resource.actions.include?(:new) member do - get :edit if parent_resource.actions.include?(:edit) - get :show if parent_resource.actions.include?(:show) - put :update if parent_resource.actions.include?(:update) + get :edit if parent_resource.actions.include?(:edit) + get :show if parent_resource.actions.include?(:show) + if parent_resource.actions.include?(:update) + patch :update + put :update + end delete :destroy if parent_resource.actions.include?(:destroy) end end @@ -1260,6 +1294,9 @@ Calling with multiple paths is deprecated. parent_resource.instance_of?(Resource) && @scope[:shallow] end + # match 'path' => 'controller#action' + # match 'path', to: 'controller#action' + # match 'path', 'otherpath', on: :member, via: :get def match(path, *rest) if rest.empty? && Hash === path options = path diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ac4dd7d927..30e9e5634b 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -9,6 +9,12 @@ require 'action_controller/metal/exceptions' module ActionDispatch module Routing class RouteSet #:nodoc: + # Since the router holds references to many parts of the system + # like engines, controllers and the application itself, inspecting + # the route set can actually be really slow, therefore we default + # alias inspect to to_s. + alias inspect to_s + PARAMETERS_KEY = 'action_dispatch.request.path_parameters' class Dispatcher #:nodoc: @@ -31,6 +37,7 @@ module ActionDispatch end def prepare_params!(params) + normalize_controller!(params) merge_default_action!(params) split_glob_param!(params) if @glob_param end @@ -66,6 +73,10 @@ module ActionDispatch controller.action(action).call(env) end + def normalize_controller!(params) + params[:controller] = params[:controller].underscore if params.key?(:controller) + end + def merge_default_action!(params) params[:action] ||= 'index' end @@ -180,14 +191,50 @@ module ActionDispatch selector = url_helper_name(name, kind) hash_access_method = hash_access_name(name, kind) - @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 - remove_possible_method :#{selector} - def #{selector}(*args) - url_for(#{hash_access_method}(*args)) - end - END_EVAL + if optimize_helper?(route) + @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + remove_possible_method :#{selector} + def #{selector}(*args) + if args.size == #{route.required_parts.size} && !args.last.is_a?(Hash) && optimize_routes_generation? + options = #{options.inspect}.merge!(url_options) + options[:path] = "#{optimized_helper(route)}" + ActionDispatch::Http::URL.url_for(options) + else + url_for(#{hash_access_method}(*args)) + end + end + END_EVAL + else + @module.module_eval <<-END_EVAL, __FILE__, __LINE__ + 1 + remove_possible_method :#{selector} + def #{selector}(*args) + url_for(#{hash_access_method}(*args)) + end + END_EVAL + end + helpers << selector end + + # Clause check about when we need to generate an optimized helper. + def optimize_helper?(route) #:nodoc: + route.ast.grep(Journey::Nodes::Star).empty? && route.requirements.except(:controller, :action).empty? + end + + # Generates the interpolation to be used in the optimized helper. + def optimized_helper(route) + string_route = route.ast.to_s + + while string_route.gsub!(/\([^\)]*\)/, "") + true + end + + route.required_parts.each_with_index do |part, i| + string_route.gsub!(part.inspect, "\#{Journey::Router::Utils.escape_fragment(args[#{i}].to_param)}") + end + + string_route + end end attr_accessor :formatter, :set, :named_routes, :default_scope, :router @@ -246,8 +293,7 @@ module ActionDispatch def eval_block(block) if block.arity == 1 raise "You are using the old router DSL which has been removed in Rails 3.1. " << - "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/ " << - "or add the rails_legacy_mapper gem to your Gemfile" + "Please check how to update your routes file at: http://www.engineyard.com/blog/2010/the-lowdown-on-routes-in-rails-3/" end mapper = Mapper.new(self) if default_scope @@ -313,7 +359,7 @@ module ActionDispatch # Rails.application.routes.url_helpers.url_for(args) @_routes = routes class << self - delegate :url_for, :to => '@_routes' + delegate :url_for, :optimize_routes_generation?, :to => '@_routes' end # Make named_routes available in the module singleton @@ -482,7 +528,7 @@ module ActionDispatch # if the current controller is "foo/bar/baz" and :controller => "baz/bat" # is specified, the controller becomes "foo/baz/bat" def use_relative_controller! - if !named_route && different_controller? + if !named_route && different_controller? && !controller.start_with?("/") old_parts = current_controller.split('/') size = controller.count("/") + 1 parts = old_parts[0...-size] << controller @@ -547,6 +593,14 @@ module ActionDispatch RESERVED_OPTIONS = [:host, :protocol, :port, :subdomain, :domain, :tld_length, :trailing_slash, :anchor, :params, :only_path, :script_name] + def mounted? + false + end + + def optimize_routes_generation? + !mounted? && default_url_options.empty? + end + def _generate_prefix(options = {}) nil end @@ -558,18 +612,17 @@ module ActionDispatch user, password = extract_authentication(options) path_segments = options.delete(:_path_segments) - script_name = options.delete(:script_name) - - path = (script_name.blank? ? _generate_prefix(options) : script_name.chomp('/')).to_s + script_name = options.delete(:script_name).presence || _generate_prefix(options) path_options = options.except(*RESERVED_OPTIONS) path_options = yield(path_options) if block_given? - path_addition, params = generate(path_options, path_segments || {}) - path << path_addition + path, params = generate(path_options, path_segments || {}) + params.merge!(options[:params] || {}) ActionDispatch::Http::URL.url_for(options.merge!({ :path => path, + :script_name => script_name, :params => params, :user => user, :password => password @@ -583,6 +636,7 @@ module ActionDispatch def recognize_path(path, environment = {}) method = (environment[:method] || "GET").to_s.upcase path = Journey::Router::Utils.normalize_path(path) unless path =~ %r{://} + extras = environment[:extras] || {} begin env = Rack::MockRequest.env_for(path, {:method => method}) @@ -592,6 +646,7 @@ module ActionDispatch req = @request_class.new(env) @router.recognize(req) do |route, matches, params| + params.merge!(extras) params.each do |key, value| if value.is_a?(String) value = value.dup.force_encoding(Encoding::BINARY) diff --git a/actionpack/lib/action_dispatch/routing/url_for.rb b/actionpack/lib/action_dispatch/routing/url_for.rb index ee6616c5d3..94db36ce1f 100644 --- a/actionpack/lib/action_dispatch/routing/url_for.rb +++ b/actionpack/lib/action_dispatch/routing/url_for.rb @@ -102,6 +102,9 @@ module ActionDispatch super end + # Hook overriden in controller to add request information + # with `default_url_options`. Application logic should not + # go into url_options. def url_options default_url_options end @@ -152,6 +155,11 @@ module ActionDispatch protected + def optimize_routes_generation? + return @_optimized_routes if defined?(@_optimized_routes) + @_optimized_routes = _routes.optimize_routes_generation? && default_url_options.empty? + end + def _with_routes(routes) old_routes, @_routes = @_routes, routes yield diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 094cfbfc76..a5e7a8c715 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -83,7 +83,7 @@ module ActionDispatch refer else @controller.url_for(fragment) - end.gsub(/[\r\n]/, '') + end.gsub(/[\0\r\n]/, '') end end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 1552676fbb..1f4b905d18 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -39,10 +39,9 @@ module ActionDispatch # # Test a custom route # assert_recognizes({:controller => 'items', :action => 'show', :id => '1'}, 'view/item1') def assert_recognizes(expected_options, path, extras={}, message=nil) - request = recognized_request_for(path) + request = recognized_request_for(path, extras) expected_options = expected_options.clone - extras.each_key { |key| expected_options.delete key } unless extras.nil? expected_options.stringify_keys! @@ -181,7 +180,7 @@ module ActionDispatch private # Recognizes the route for a given path. - def recognized_request_for(path) + def recognized_request_for(path, extras = {}) if path.is_a?(Hash) method = path[:method] path = path[:path] @@ -209,7 +208,7 @@ module ActionDispatch request.request_method = method if method - params = @routes.recognize_path(path, { :method => method }) + params = @routes.recognize_path(path, { :method => method, :extras => extras }) request.path_parameters = params.with_indifferent_access request diff --git a/actionpack/lib/action_dispatch/testing/assertions/selector.rb b/actionpack/lib/action_dispatch/testing/assertions/selector.rb index 33796008bd..8eed85bce2 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/selector.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/selector.rb @@ -340,8 +340,8 @@ module ActionDispatch # element +encoded+. It then calls the block with all un-encoded elements. # # ==== Examples - # # Selects all bold tags from within the title of an ATOM feed's entries (perhaps to nab a section name prefix) - # assert_select_feed :atom, 1.0 do + # # Selects all bold tags from within the title of an Atom feed's entries (perhaps to nab a section name prefix) + # assert_select "feed[xmlns='http://www.w3.org/2005/Atom']" do # # Select each entry item and then the title item # assert_select "entry>title" do # # Run assertions on the encoded title elements @@ -353,7 +353,7 @@ module ActionDispatch # # # # Selects all paragraph tags from within the description of an RSS feed - # assert_select_feed :rss, 2.0 do + # assert_select "rss[version=2.0]" do # # Select description element of each feed item. # assert_select "channel>item>description" do # # Run assertions on the encoded elements. diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 08b7ff49c2..69d54f6981 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -26,8 +26,8 @@ module ActionDispatch # object's <tt>@response</tt> instance variable will point to the same # response object. # - # You can also perform POST, PUT, DELETE, and HEAD requests with +#post+, - # +#put+, +#delete+, and +#head+. + # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with + # +#post+, +#patch+, +#put+, +#delete+, and +#head+. def get(path, parameters = nil, headers = nil) process :get, path, parameters, headers end @@ -38,6 +38,12 @@ module ActionDispatch process :post, path, parameters, headers end + # Performs a PATCH request with the given parameters. See +#get+ for more + # details. + def patch(path, parameters = nil, headers = nil) + process :patch, path, parameters, headers + end + # Performs a PUT request with the given parameters. See +#get+ for more # details. def put(path, parameters = nil, headers = nil) @@ -56,13 +62,19 @@ module ActionDispatch process :head, path, parameters, headers end + # Performs a OPTIONS request with the given parameters. See +#get+ for + # more details. + def options(path, parameters = nil, headers = nil) + process :options, path, parameters, headers + end + # Performs an XMLHttpRequest request with the given parameters, mirroring # a request from the Prototype library. # - # The request_method is +:get+, +:post+, +:put+, +:delete+ or +:head+; the - # parameters are +nil+, a hash, or a url-encoded or multipart string; - # the headers are a hash. Keys are automatically upcased and prefixed - # with 'HTTP_' if not already. + # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or + # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart + # string; the headers are a hash. Keys are automatically upcased and + # prefixed with 'HTTP_' if not already. def xml_http_request(request_method, path, parameters = nil, headers = nil) headers ||= {} headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' @@ -102,6 +114,12 @@ module ActionDispatch request_via_redirect(:post, path, parameters, headers) end + # Performs a PATCH request, following any subsequent redirect. + # See +request_via_redirect+ for more information. + def patch_via_redirect(path, parameters = nil, headers = nil) + request_via_redirect(:patch, path, parameters, headers) + end + # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. def put_via_redirect(path, parameters = nil, headers = nil) @@ -312,7 +330,7 @@ module ActionDispatch @integration_session = Integration::Session.new(app) end - %w(get post put head delete cookies assigns + %w(get post patch put head delete options cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| reset! unless integration_session diff --git a/actionpack/lib/action_dispatch/testing/test_process.rb b/actionpack/lib/action_dispatch/testing/test_process.rb index b08ff41950..3a6d081721 100644 --- a/actionpack/lib/action_dispatch/testing/test_process.rb +++ b/actionpack/lib/action_dispatch/testing/test_process.rb @@ -5,7 +5,8 @@ require 'active_support/core_ext/hash/indifferent_access' module ActionDispatch module TestProcess def assigns(key = nil) - assigns = @controller.view_assigns.with_indifferent_access + assigns = {}.with_indifferent_access + @controller.view_assigns.each {|k, v| assigns.regular_writer(k, v)} key.nil? ? assigns : assigns[key] end diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index 2a28e780bf..add8d94b70 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -117,7 +117,7 @@ module ActionView end def relative_url_root - config.relative_url_root + config.relative_url_root || current_request.try(:script_name) end def asset_host_config diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 1187956081..e27111012d 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -39,7 +39,7 @@ module ActionView private def object_has_errors? - object.respond_to?(:errors) && object.errors.respond_to?(:full_messages) && error_message.any? + object.respond_to?(:errors) && object.errors.respond_to?(:[]) && error_message.present? end def tag_generate_errors?(options) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 40a950c644..6dd52d8186 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -24,9 +24,10 @@ module ActionView # server by setting ActionController::Base.asset_host in the application # configuration, typically in <tt>config/environments/production.rb</tt>. # For example, you'd define <tt>assets.example.com</tt> to be your asset - # host this way: + # host this way, inside the <tt>configure</tt> block of your environment-specific + # configuration files or <tt>config/application.rb</tt>: # - # ActionController::Base.asset_host = "assets.example.com" + # config.action_controller.asset_host = "assets.example.com" # # Helpers take that into account: # @@ -198,7 +199,7 @@ module ActionView include JavascriptTagHelpers include StylesheetTagHelpers # Returns a link tag that browsers and news readers can use to auto-detect - # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or + # an RSS or Atom feed. The +type+ can either be <tt>:rss</tt> (default) or # <tt>:atom</tt>. Control the link options in url_for format using the # +url_options+. You can modify the LINK tag itself in +tag_options+. # @@ -278,6 +279,13 @@ module ActionView end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route + # Computes the full URL to an image asset in the public images directory. + # This will use +image_path+ internally, so most of their behaviors will be the same. + def image_url(source) + URI.join(current_host, path_to_image(source)).to_s + end + alias_method :url_to_image, :image_url # aliased to avoid conflicts with an image_url named route + # Computes the path to a video asset in the public videos directory. # Full paths from the document root will be passed through. # Used internally by +video_tag+ to build the video path. @@ -293,6 +301,13 @@ module ActionView end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route + # Computes the full URL to a video asset in the public videos directory. + # This will use +video_path+ internally, so most of their behaviors will be the same. + def video_url(source) + URI.join(current_host, path_to_video(source)).to_s + end + alias_method :url_to_video, :video_url # aliased to avoid conflicts with an video_url named route + # Computes the path to an audio asset in the public audios directory. # Full paths from the document root will be passed through. # Used internally by +audio_tag+ to build the audio path. @@ -308,6 +323,13 @@ module ActionView end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route + # Computes the full URL to a audio asset in the public audios directory. + # This will use +audio_path+ internally, so most of their behaviors will be the same. + def audio_url(source) + URI.join(current_host, path_to_audio(source)).to_s + end + alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route + # Computes the path to a font asset in the public fonts directory. # Full paths from the document root will be passed through. # @@ -322,6 +344,13 @@ module ActionView end alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route + # Computes the full URL to a font asset in the public fonts directory. + # This will use +font_path+ internally, so most of their behaviors will be the same. + def font_url(source) + URI.join(current_host, path_to_font(source)).to_s + end + alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route + # Returns an html image tag for the +source+. The +source+ can be a full # path or a file that exists in your public images directory. # @@ -460,9 +489,13 @@ module ActionView end else options[:src] = send("path_to_#{type}", sources.first) - tag(type, options) + content_tag(type, nil, options) end end + + def current_host + url_for(:only_path => false) + end end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index d9f1f88ade..c67f81dcf4 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -87,6 +87,13 @@ module ActionView end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route + # Computes the full URL to a javascript asset in the public javascripts directory. + # This will use +javascript_path+ internally, so most of their behaviors will be the same. + def javascript_url(source) + URI.join(current_host, path_to_javascript(source)).to_s + end + alias_method :url_to_javascript, :javascript_url # aliased to avoid conflicts with a javascript_url named route + # Returns an HTML script tag for each of the +sources+ provided. # # Sources may be paths to JavaScript files. Relative paths are assumed to be relative diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index 41958c6559..2584b67548 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -65,6 +65,13 @@ module ActionView end alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with a stylesheet_path named route + # Computes the full URL to a stylesheet asset in the public stylesheets directory. + # This will use +stylesheet_path+ internally, so most of their behaviors will be the same. + def stylesheet_url(source) + URI.join(current_host, path_to_stylesheet(source)).to_s + end + alias_method :url_to_stylesheet, :stylesheet_url # aliased to avoid conflicts with a stylesheet_url named route + # Returns a stylesheet link tag for the sources specified as arguments. If # you don't specify an extension, <tt>.css</tt> will be appended automatically. # You can modify the link attributes by passing a hash as the last argument. diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 17bbfe2efd..278139cadb 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -215,7 +215,7 @@ module ActionView def flush_output_buffer #:nodoc: if output_buffer && !output_buffer.empty? response.body_parts << output_buffer - self.output_buffer = output_buffer[0,0] + self.output_buffer = output_buffer.respond_to?(:clone_empty) ? output_buffer.clone_empty : output_buffer[0, 0] nil end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 2d37923825..45e5a862b6 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -625,13 +625,18 @@ module ActionView # time_tag Date.today, :pubdate => true # => # <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time> # - def time_tag(date_or_time, *args) + # <%= time_tag Time.now do %> + # <span>Right now</span> + # <% end %> + # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> + # + def time_tag(date_or_time, *args, &block) options = args.extract_options! format = options.delete(:format) || :long content = args.first || I18n.l(date_or_time, :format => format) datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.rfc3339 - content_tag(:time, content, options.reverse_merge(:datetime => datetime)) + content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) end end @@ -669,11 +674,7 @@ module ActionView @options[:discard_minute] ||= true if @options[:discard_hour] @options[:discard_second] ||= true unless @options[:include_seconds] && !@options[:discard_minute] - # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are - # valid (otherwise it could be 31 and February wouldn't be a valid date) - if @datetime && @options[:discard_day] && !@options[:discard_month] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded if @options[:tag] && @options[:ignore_date] select_time @@ -696,11 +697,7 @@ module ActionView @options[:discard_month] ||= true unless order.include?(:month) @options[:discard_day] ||= true if @options[:discard_month] || !order.include?(:day) - # If the day is hidden and the month is visible, the day should be set to the 1st so all month choices are - # valid (otherwise it could be 31 and February wouldn't be a valid date) - if @datetime && @options[:discard_day] && !@options[:discard_month] - @datetime = @datetime.change(:day => 1) - end + set_day_if_discarded [:day, :month, :year].each { |o| order.unshift(o) unless order.include?(o) } @@ -802,6 +799,14 @@ module ActionView end end + # If the day is hidden, the day should be set to the 1st so all month and year choices are + # valid. Otherwise, February 31st or February 29th, 2011 can be selected, which are invalid. + def set_day_if_discarded + if @datetime && @options[:discard_day] + @datetime = @datetime.change(:day => 1) + end + end + # Returns translated month names, but also ensures that a custom month # name array has a leading nil element. def month_names @@ -972,7 +977,10 @@ module ActionView # Returns the id attribute for the input tag. # => "post_written_on_1i" def input_id_from_type(type) - input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') + id = input_name_from_type(type).gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') + id = @options[:namespace] + '_' + id if @options[:namespace] + + id end # Given an ordering of datetime components, create the selection HTML diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index bdfef920c5..6219a7a924 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -9,6 +9,7 @@ require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/array/extract_options' +require 'active_support/deprecation' module ActionView # = Action View Form Helpers @@ -16,17 +17,28 @@ module ActionView # Form helpers are designed to make working with resources much easier # compared to using vanilla HTML. # - # Forms for models are created with +form_for+. That method yields a form - # builder that knows the model the form is about. The form builder is thus - # able to generate default values for input fields that correspond to model - # attributes, and also convenient names, IDs, endpoints, etc. + # Typically, a form designed to create or update a resource reflects the + # identity of the resource in several ways: (i) the url that the form is + # sent to (the form element's +action+ attribute) should result in a request + # being routed to the appropriate controller action (with the appropriate <tt>:id</tt> + # parameter in the case of an existing resource), (ii) input fields should + # be named in such a way that in the controller their values appear in the + # appropriate places within the +params+ hash, and (iii) for an existing record, + # when the form is initially displayed, input fields corresponding to attributes + # of the resource should show the current values of those attributes. # - # Conventions in the generated field names allow controllers to receive form - # data nicely structured in +params+ with no effort on your side. + # In Rails, this is usually achieved by creating the form using +form_for+ and + # a number of related helper methods. +form_for+ generates an appropriate <tt>form</tt> + # tag and yields a form builder object that knows the model the form is about. + # Input fields are created by calling methods defined on the form builder, which + # means they are able to generate the appropriate names and default values + # corresponding to the model attributes, as well as convenient IDs, etc. + # Conventions in the generated field names allow controllers to receive form data + # nicely structured in +params+ with no effort on your side. # # For example, to create a new person you typically set up a new instance of # +Person+ in the <tt>PeopleController#new</tt> action, <tt>@person</tt>, and - # pass it to +form_for+: + # in the view template pass that object to +form_for+: # # <%= form_for @person do |f| %> # <%= f.label :first_name %>: @@ -45,10 +57,10 @@ module ActionView # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: - # <input id="person_first_name" name="person[first_name]" size="30" type="text" /><br /> + # <input id="person_first_name" name="person[first_name]" type="text" /><br /> # # <label for="person_last_name">Last name</label>: - # <input id="person_last_name" name="person[last_name]" size="30" type="text" /><br /> + # <input id="person_last_name" name="person[last_name]" type="text" /><br /> # # <input name="commit" type="submit" value="Create Person" /> # </form> @@ -76,10 +88,10 @@ module ActionView # <input name="authenticity_token" type="hidden" value="NrOp5bsjoLRuK8IW5+dQEYjKGUJDe7TQoZVvq95Wteg=" /> # </div> # <label for="person_first_name">First name</label>: - # <input id="person_first_name" name="person[first_name]" size="30" type="text" value="John" /><br /> + # <input id="person_first_name" name="person[first_name]" type="text" value="John" /><br /> # # <label for="person_last_name">Last name</label>: - # <input id="person_last_name" name="person[last_name]" size="30" type="text" value="Smith" /><br /> + # <input id="person_last_name" name="person[last_name]" type="text" value="Smith" /><br /> # # <input name="commit" type="submit" value="Update Person" /> # </form> @@ -109,29 +121,14 @@ module ActionView object.respond_to?(:to_model) ? object.to_model : object end - # Creates a form and a scope around a specific model object that is used - # as a base for questioning about values for the fields. + # Creates a form that allows the user to create or update the attributes + # of a specific model object. # - # Rails provides succinct resource-oriented form generation with +form_for+ - # like this: - # - # <%= form_for @offer do |f| %> - # <%= f.label :version, 'Version' %>: - # <%= f.text_field :version %><br /> - # <%= f.label :author, 'Author' %>: - # <%= f.text_field :author %><br /> - # <%= f.submit %> - # <% end %> - # - # There, +form_for+ is able to generate the rest of RESTful form - # parameters based on introspection on the record, but to understand what - # it does we need to dig first into the alternative generic usage it is - # based upon. - # - # === Generic form_for - # - # The generic way to call +form_for+ yields a form builder around a - # model: + # The method can be used in several slightly different ways, depending on + # how much you wish to rely on Rails to infer automatically from the model + # how the form should be constructed. For a generic model object, a form + # can be created by passing +form_for+ a string or symbol representing + # the object we are concerned with: # # <%= form_for :person do |f| %> # First name: <%= f.text_field :first_name %><br /> @@ -141,24 +138,39 @@ module ActionView # <%= f.submit %> # <% end %> # - # There, the argument is a symbol or string with the name of the - # object the form is about. - # - # The form builder acts as a regular form helper that somehow carries the - # model. Thus, the idea is that + # The variable +f+ yielded to the block is a FormBuilder object that + # incorporates the knowledge about the model object represented by + # <tt>:person</tt> passed to +form_for+. Methods defined on the FormBuilder + # are used to generate fields bound to this model. Thus, for example, # # <%= f.text_field :first_name %> # - # gets expanded to + # will get expanded to # # <%= text_field :person, :first_name %> + # which results in an html <tt><input></tt> tag whose +name+ attribute is + # <tt>person[first_name]</tt>. This means that when the form is submitted, + # the value entered by the user will be available in the controller as + # <tt>params[:person][:first_name]</tt>. + # + # For fields generated in this way using the FormBuilder, + # if <tt>:person</tt> also happens to be the name of an instance variable + # <tt>@person</tt>, the default value of the field shown when the form is + # initially displayed (e.g. in the situation where you are editing an + # existing record) will be the value of the corresponding attribute of + # <tt>@person</tt>. # # The rightmost argument to +form_for+ is an - # optional hash of options: - # - # * <tt>:url</tt> - The URL the form is submitted to. It takes the same - # fields you pass to +url_for+ or +link_to+. In particular you may pass - # here a named route directly as well. Defaults to the current action. + # optional hash of options - + # + # * <tt>:url</tt> - The URL the form is to be submitted to. This may be + # represented in the same way as values passed to +url_for+ or +link_to+. + # So for example you may use a named route directly. When the model is + # represented by a string or symbol, as in the example above, if the + # <tt>:url</tt> option is not specified, by default the form will be + # sent back to the current url (We will describe below an alternative + # resource-oriented usage of +form_for+ in which the URL does not need + # to be specified explicitly). # * <tt>:namespace</tt> - A namespace for your form to ensure uniqueness of # id attributes on form elements. The namespace attribute will be prefixed # with underscore on the generated HTML id. @@ -168,7 +180,7 @@ module ActionView # possible to use both the stand-alone FormHelper methods and methods # from FormTagHelper. For example: # - # <%= form_for @person do |f| %> + # <%= form_for :person do |f| %> # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> @@ -180,26 +192,65 @@ module ActionView # are designed to work with an object as base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. # - # === Resource-oriented style + # === #form_for with a model object + # + # In the examples above, the object to be created or edited was + # represented by a symbol passed to +form_for+, and we noted that + # a string can also be used equivalently. It is also possible, however, + # to pass a model object itself to +form_for+. For example, if <tt>@post</tt> + # is an existing record you wish to edit, you can create the form using + # + # <%= form_for @post do |f| %> + # ... + # <% end %> + # + # This behaves in almost the same way as outlined previously, with a + # couple of small exceptions. First, the prefix used to name the input + # elements within the form (hence the key that denotes them in the +params+ + # hash) is actually derived from the object's _class_, e.g. <tt>params[:post]</tt> + # if the object's class is +Post+. However, this can be overwritten using + # the <tt>:as</tt> option, e.g. - + # + # <%= form_for(@person, :as => :client) do |f| %> + # ... + # <% end %> + # + # would result in <tt>params[:client]</tt>. + # + # Secondly, the field values shown when the form is initially displayed + # are taken from the attributes of the object passed to +form_for+, + # regardless of whether the object is an instance + # variable. So, for example, if we had a _local_ variable +post+ + # representing an existing record, + # + # <%= form_for post do |f| %> + # ... + # <% end %> # - # As we said above, in addition to manually configuring the +form_for+ - # call, you can rely on automated resource identification, which will use - # the conventions and named routes of that approach. This is the - # preferred way to use +form_for+ nowadays. + # would produce a form with fields whose initial state reflect the current + # values of the attributes of +post+. # - # For example, if <tt>@post</tt> is an existing record you want to edit + # === Resource-oriented style + # + # In the examples just shown, although not indicated explicitly, we still + # need to use the <tt>:url</tt> option in order to specify where the + # form is going to be sent. However, further simplification is possible + # if the record passed to +form_for+ is a _resource_, i.e. it corresponds + # to a set of RESTful routes, e.g. defined using the +resources+ method + # in <tt>config/routes.rb</tt>. In this case Rails will simply infer the + # appropriate URL from the record itself. For example, # # <%= form_for @post do |f| %> # ... # <% end %> # - # is equivalent to something like: + # 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| %> # ... # <% end %> # - # And for new records + # And for a new record # # <%= form_for(Post.new) do |f| %> # ... @@ -211,7 +262,7 @@ module ActionView # ... # <% end %> # - # You can also overwrite the individual conventions, like this: + # However you can still overwrite individual conventions, such as: # # <%= form_for(@post, :url => super_posts_path) do |f| %> # ... @@ -223,13 +274,6 @@ module ActionView # ... # <% end %> # - # If you have an object that needs to be represented as a different - # parameter, like a Person that acts as a Client: - # - # <%= form_for(@person, :as => :client) do |f| %> - # ... - # <% end %> - # # For namespaced routes, like +admin_post_url+: # # <%= form_for([:admin, @post]) do |f| %> @@ -250,11 +294,11 @@ module ActionView # # You can force the form to use the full array of HTTP verbs by setting # - # :method => (:get|:post|:put|:delete) + # :method => (:get|:post|:patch|:put|:delete) # - # in the options hash. If the verb is not GET or POST, which are natively supported by HTML forms, the - # form will be set to POST and a hidden input called _method will carry the intended verb for the server - # to interpret. + # in the options hash. If the verb is not GET or POST, which are natively + # supported by HTML forms, the form will be set to POST and a hidden input + # called _method will carry the intended verb for the server to interpret. # # === Unobtrusive JavaScript # @@ -385,7 +429,7 @@ module ActionView object = convert_to_model(object) as = options[:as] - action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :put] : [:new, :post] + action, method = object.respond_to?(:persisted?) && object.persisted? ? [:edit, :patch] : [:new, :post] options[:html].reverse_merge!( :class => as ? "#{action}_#{as}" : dom_class(object, action), :id => as ? "#{action}_#{as}" : [options[:namespace], dom_id(object, action)].compact.join("_").presence, @@ -402,30 +446,59 @@ module ActionView # # === Generic Examples # + # Although the usage and purpose of +field_for+ is similar to +form_for+'s, + # its method signature is slightly different. Like +form_for+, it yields + # a FormBuilder object associated with a particular model object to a block, + # and within the block allows methods to be called on the builder to + # generate fields associated with the model object. Fields may reflect + # a model object in two ways - how they are named (hence how submitted + # values appear within the +params+ hash in the controller) and what + # default values are shown when the form the fields appear in is first + # displayed. In order for both of these features to be specified independently, + # both an object name (represented by either a symbol or string) and the + # object itself can be passed to the method separately - + # # <%= form_for @person do |person_form| %> # First name: <%= person_form.text_field :first_name %> # Last name : <%= person_form.text_field :last_name %> # - # <%= fields_for @person.permission do |permission_fields| %> + # <%= fields_for :permission, @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> # # <%= f.submit %> # <% end %> # - # ...or if you have an object that needs to be represented as a different - # parameter, like a Client that acts as a Person: + # In this case, the checkbox field will be represented by an HTML +input+ + # tag with the +name+ attribute <tt>permission[admin]</tt>, and the submitted + # value will appear in the controller as <tt>params[:permission][:admin]</tt>. + # If <tt>@person.permission</tt> is an existing record with an attribute + # +admin+, the initial state of the checkbox when first displayed will + # reflect the value of <tt>@person.permission.admin</tt>. + # + # Often this can be simplified by passing just the name of the model + # object to +fields_for+ - # - # <%= fields_for :person, @client do |permission_fields| %> + # <%= fields_for :permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # - # ...or if you don't have an object, just a name of the parameter: + # ...in which case, if <tt>:permission</tt> also happens to be the name of an + # instance variable <tt>@permission</tt>, the initial state of the input + # field will reflect the value of that variable's attribute <tt>@permission.admin</tt>. # - # <%= fields_for :person do |permission_fields| %> + # Alternatively, you can pass just the model object itself (if the first + # argument isn't a string or symbol +fields_for+ will realize that the + # name has been omitted) - + # + # <%= fields_for @person.permission do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # + # and +fields_for+ will derive the required name of the field from the + # _class_ of the model object, e.g. if <tt>@person.permission</tt>, is + # of class +Permission+, the field will still be named <tt>permission[admin]</tt>. + # # Note: This also works for the methods in FormOptionHelper and # DateHelper that are designed to work with an object as base, like # FormOptionHelper#collection_select and DateHelper#datetime_select. @@ -860,20 +933,20 @@ module ActionView # ==== Examples # # search_field(:user, :name) - # # => <input id="user_name" name="user[name]" size="30" type="search" /> + # # => <input id="user_name" name="user[name]" type="search" /> # search_field(:user, :name, :autosave => false) - # # => <input autosave="false" id="user_name" name="user[name]" size="30" type="search" /> + # # => <input autosave="false" id="user_name" name="user[name]" type="search" /> # search_field(:user, :name, :results => 3) - # # => <input id="user_name" name="user[name]" results="3" size="30" type="search" /> + # # => <input id="user_name" name="user[name]" results="3" type="search" /> # # Assume request.host returns "www.example.com" # search_field(:user, :name, :autosave => true) - # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" size="30" type="search" /> + # # => <input autosave="com.example.www" id="user_name" name="user[name]" results="10" type="search" /> # search_field(:user, :name, :onsearch => true) - # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" /> + # # => <input id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> # search_field(:user, :name, :autosave => false, :onsearch => true) - # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" size="30" type="search" /> + # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> # search_field(:user, :name, :autosave => true, :onsearch => true) - # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" size="30" type="search" /> + # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" /> # def search_field(object_name, method, options = {}) Tags::SearchField.new(object_name, method, self, options).render @@ -882,17 +955,35 @@ module ActionView # Returns a text_field of type "tel". # # telephone_field("user", "phone") - # # => <input id="user_phone" name="user[phone]" size="30" type="tel" /> + # # => <input id="user_phone" name="user[phone]" type="tel" /> # def telephone_field(object_name, method, options = {}) Tags::TelField.new(object_name, method, self, options).render end alias phone_field telephone_field + # Returns a text_field of type "date". + # + # date_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="date" /> + # + # The default value is generated by trying to call "to_date" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. You can still override that + # by passing the "value" option explicitly, e.g. + # + # @user.born_on = Date.new(1984, 1, 27) + # date_field("user", "born_on", value: "1984-05-12") + # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-05-12" /> + # + def date_field(object_name, method, options = {}) + Tags::DateField.new(object_name, method, self, options).render + end + # Returns a text_field of type "url". # # url_field("user", "homepage") - # # => <input id="user_homepage" size="30" name="user[homepage]" type="url" /> + # # => <input id="user_homepage" name="user[homepage]" type="url" /> # def url_field(object_name, method, options = {}) Tags::UrlField.new(object_name, method, self, options).render @@ -901,7 +992,7 @@ module ActionView # Returns a text_field of type "email". # # email_field("user", "address") - # # => <input id="user_address" size="30" name="user[address]" type="email" /> + # # => <input id="user_address" name="user[address]" type="email" /> # def email_field(object_name, method, options = {}) Tags::EmailField.new(object_name, method, self, options).render @@ -967,7 +1058,12 @@ module ActionView self end - def initialize(object_name, object, template, options) + def initialize(object_name, object, template, options, block=nil) + if block + ActiveSupport::Deprecation.warn( + "Giving a block to FormBuilder is deprecated and has no effect anymore.") + end + @nested_child_index = {} @object_name, @object, @template, @options = object_name, object, template, options @parent_builder = options[:parent_builder] @@ -998,7 +1094,7 @@ module ActionView 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] = fields_options[:parent_builder].options[:namespace] + fields_options[:namespace] = options[:namespace] case record_name when String, Symbol @@ -1084,14 +1180,14 @@ module ActionView # <% end %> # # In the example above, if @post is a new record, it will use "Create Post" as - # submit button label, otherwise, it uses "Update Post". + # button label, otherwise, it uses "Update Post". # - # Those labels can be customized using I18n, under the helpers.submit key and accept - # the %{model} as translation interpolation: + # Those labels can be customized using I18n, under the helpers.submit key + # (the same as submit helper) and accept the %{model} as translation interpolation: # # en: # helpers: - # button: + # submit: # create: "Create a %{model}" # update: "Confirm changes to %{model}" # @@ -1099,7 +1195,7 @@ module ActionView # # en: # helpers: - # button: + # submit: # post: # create: "Add %{model}" # diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c4cdfef4a2..d61c2bbee2 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -153,6 +153,8 @@ module ActionView # form, and parameters extraction gets the last occurrence of any repeated # key in the query string, that works for ordinary forms. # + # In case if you don't want the helper to generate this hidden field you can specify <tt>:include_blank => false</tt> option. + # def select(object, method, choices, options = {}, html_options = {}) Tags::Select.new(object, method, self, choices, options, html_options).render end @@ -164,7 +166,9 @@ module ActionView # # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are methods to be called on each member # of +collection+. The return values are used as the +value+ attribute and contents of each - # <tt><option></tt> tag, respectively. + # <tt><option></tt> tag, respectively. They can also be any object that responds to +call+, such + # as a +proc+, that will be called for each member of the +collection+ to + # retrieve the value/text. # # Example object structure for use with this method: # class Post < ActiveRecord::Base @@ -328,9 +332,12 @@ module ActionView container.map do |element| html_attributes = option_html_attributes(element) text, value = option_text_and_value(element).map { |item| item.to_s } - selected_attribute = ' selected="selected"' if option_value_selected?(value, selected) - disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled) - %(<option value="#{ERB::Util.html_escape(value)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{ERB::Util.html_escape(text)}</option>) + + html_attributes[:selected] = 'selected' if option_value_selected?(value, selected) + html_attributes[:disabled] = 'disabled' if disabled && option_value_selected?(value, disabled) + html_attributes[:value] = value + + content_tag(:option, text, html_attributes) end.join("\n").html_safe end @@ -360,12 +367,13 @@ module ActionView # should produce the desired results. def options_from_collection_for_select(collection, value_method, text_method, selected = nil) options = collection.map do |element| - [element.send(text_method), element.send(value_method)] + [value_for_collection(element, text_method), value_for_collection(element, value_method)] end selected, disabled = extract_selected_and_disabled(selected) - select_deselect = {} - select_deselect[:selected] = extract_values_from_collection(collection, value_method, selected) - select_deselect[:disabled] = extract_values_from_collection(collection, value_method, disabled) + select_deselect = { + :selected => extract_values_from_collection(collection, value_method, selected), + :disabled => extract_values_from_collection(collection, value_method, disabled) + } options_for_select(options, select_deselect) end @@ -418,10 +426,10 @@ module ActionView # wrap the output in an appropriate <tt><select></tt> tag. def option_groups_from_collection_for_select(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key = nil) collection.map do |group| - group_label_string = group.send(group_label_method) - "<optgroup label=\"#{ERB::Util.html_escape(group_label_string)}\">" + - options_from_collection_for_select(group.send(group_method), option_key_method, option_value_method, selected_key) + - '</optgroup>' + option_tags = options_from_collection_for_select( + group.send(group_method), option_key_method, option_value_method, selected_key) + + content_tag(:optgroup, option_tags, :label => group.send(group_label_method)) end.join.html_safe end @@ -469,16 +477,16 @@ module ActionView # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to # wrap the output in an appropriate <tt><select></tt> tag. def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil) - body = '' - body << content_tag(:option, prompt, { :value => "" }, true) if prompt + body = "".html_safe + body.safe_concat content_tag(:option, prompt, :value => "") if prompt grouped_options = grouped_options.sort if grouped_options.is_a?(Hash) - grouped_options.each do |group| - body << content_tag(:optgroup, options_for_select(group[1], selected_key), :label => group[0]) + grouped_options.each do |label, container| + body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label) end - body.html_safe + body end # Returns a string of option tags for pretty much any time zone in the @@ -500,42 +508,162 @@ module ActionView # NOTE: Only the option tags are returned, you have to wrap this call in # a regular HTML select tag. def time_zone_options_for_select(selected = nil, priority_zones = nil, model = ::ActiveSupport::TimeZone) - zone_options = "" + zone_options = "".html_safe zones = model.all convert_zones = lambda { |list| list.map { |z| [ z.to_s, z.name ] } } if priority_zones if priority_zones.is_a?(Regexp) - priority_zones = model.all.find_all {|z| z =~ priority_zones} + priority_zones = zones.select { |z| z =~ priority_zones } end - zone_options += options_for_select(convert_zones[priority_zones], selected) - zone_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" - zones = zones.reject { |z| priority_zones.include?( z ) } + zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) + zone_options.safe_concat content_tag(:option, '-------------', :value => '', :disabled => 'disabled') + zone_options.safe_concat "\n" + + zones.reject! { |z| priority_zones.include?(z) } end - zone_options += options_for_select(convert_zones[zones], selected) - zone_options.html_safe + zone_options.safe_concat options_for_select(convert_zones[zones], selected) + end + + # Returns radio button tags for the collection of existing return values + # of +method+ for +object+'s class. The value returned from calling + # +method+ on the instance +object+ will be selected. If calling +method+ + # returns +nil+, no selection is made. + # + # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are + # methods to be called on each member of +collection+. The return values + # are used as the +value+ attribute and contents of each radio button tag, + # respectively. They can also be any object that responds to +call+, such + # as a +proc+, that will be called for each member of the +collection+ to + # retrieve the value/text. + # + # Example object structure for use with this method: + # class Post < ActiveRecord::Base + # belongs_to :author + # end + # class Author < ActiveRecord::Base + # has_many :posts + # def name_with_initial + # "#{first_name.first}. #{last_name}" + # end + # end + # + # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): + # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) + # + # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: + # <input id="post_author_id_1" name="post[author_id]" type="radio" value="1" checked="checked" /> + # <label for="post_author_id_1">D. Heinemeier Hansson</label> + # <input id="post_author_id_2" name="post[author_id]" type="radio" value="2" /> + # <label for="post_author_id_2">D. Thomas</label> + # <input id="post_author_id_3" name="post[author_id]" type="radio" value="3" /> + # <label for="post_author_id_3">M. Clark</label> + # + # It is also possible to customize the way the elements will be shown by + # giving a block to the method: + # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| + # b.label { b.radio_button } + # end + # + # The argument passed to the block is a special kind of builder for this + # collection, which has the ability to generate the label and radio button + # for the current item in the collection, with proper text and value. + # Using it, you can change the label and radio button display order or + # even use the label as wrapper, as in the example above. + # + # The builder methods <tt>label</tt> and <tt>radio_button</tt> also accept + # extra html options: + # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| + # b.label(:class => "radio_button") { b.radio_button(:class => "radio_button") } + # end + # + # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and + # <tt>value</tt>, which are the current item being rendered, its text and value methods, + # respectively. You can use them like this: + # collection_radio_buttons(:post, :author_id, Author.all, :id, :name_with_initial) do |b| + # b.label(:"data-value" => b.value) { b.radio_button + b.text } + # end + def collection_radio_buttons(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) + Tags::CollectionRadioButtons.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) + end + + # Returns check box tags for the collection of existing return values of + # +method+ for +object+'s class. The value returned from calling +method+ + # on the instance +object+ will be selected. If calling +method+ returns + # +nil+, no selection is made. + # + # The <tt>:value_method</tt> and <tt>:text_method</tt> parameters are + # methods to be called on each member of +collection+. The return values + # are used as the +value+ attribute and contents of each check box tag, + # respectively. They can also be any object that responds to +call+, such + # as a +proc+, that will be called for each member of the +collection+ to + # retrieve the value/text. + # + # Example object structure for use with this method: + # class Post < ActiveRecord::Base + # has_and_belongs_to_many :author + # end + # class Author < ActiveRecord::Base + # has_and_belongs_to_many :posts + # def name_with_initial + # "#{first_name.first}. #{last_name}" + # end + # end + # + # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): + # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) + # + # If <tt>@post.author_ids</tt> is already <tt>[1]</tt>, this would return: + # <input id="post_author_ids_1" name="post[author_ids][]" type="checkbox" value="1" checked="checked" /> + # <label for="post_author_ids_1">D. Heinemeier Hansson</label> + # <input id="post_author_ids_2" name="post[author_ids][]" type="checkbox" value="2" /> + # <label for="post_author_ids_2">D. Thomas</label> + # <input id="post_author_ids_3" name="post[author_ids][]" type="checkbox" value="3" /> + # <label for="post_author_ids_3">M. Clark</label> + # <input name="post[author_ids][]" type="hidden" value="" /> + # + # It is also possible to customize the way the elements will be shown by + # giving a block to the method: + # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| + # b.label { b.check_box } + # end + # + # The argument passed to the block is a special kind of builder for this + # collection, which has the ability to generate the label and check box + # for the current item in the collection, with proper text and value. + # Using it, you can change the label and check box display order or even + # use the label as wrapper, as in the example above. + # + # The builder methods <tt>label</tt> and <tt>check_box</tt> also accept + # extra html options: + # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| + # b.label(:class => "check_box") { b.check_box(:class => "check_box") } + # end + # + # There are also three special methods available: <tt>object</tt>, <tt>text</tt> and + # <tt>value</tt>, which are the current item being rendered, its text and value methods, + # respectively. You can use them like this: + # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| + # b.label(:"data-value" => b.value) { b.check_box + b.text } + # end + def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) + Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) end private def option_html_attributes(element) - return "" unless Array === element - html_attributes = [] - element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v| - html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\"" - end - html_attributes.join + return {} unless Array === element + + Hash[element.select { |e| Hash === e }.reduce({}, :merge).map { |k, v| [k, ERB::Util.html_escape(v.to_s)] }] end def option_text_and_value(option) # Options are [text, value] pairs or strings used for both. - case - when Array === option - option = option.reject { |e| Hash === e } - [option.first, option.last] - when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last) + if !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last) + option = option.reject { |e| Hash === e } if Array === option [option.first, option.last] else [option, option] @@ -543,21 +671,17 @@ module ActionView end def option_value_selected?(value, selected) - if selected.respond_to?(:include?) && !selected.is_a?(String) - selected.include? value - else - value == selected - end + Array(selected).include? value end def extract_selected_and_disabled(selected) if selected.is_a?(Proc) - [ selected, nil ] + [selected, nil] else selected = Array.wrap(selected) options = selected.extract_options!.symbolize_keys - selected_items = options.include?(:selected) ? options[:selected] : selected - [ selected_items, options[:disabled] ] + selected_items = options.fetch(:selected, selected) + [selected_items, options[:disabled]] end end @@ -570,6 +694,10 @@ module ActionView selected end end + + def value_for_collection(item, value) + value.respond_to?(:call) ? value.call(item) : item.send(value) + end end class FormBuilder @@ -588,6 +716,14 @@ module ActionView def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end + + def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}) + @template.collection_check_boxes(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) + end + + def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}) + @template.collection_radio_buttons(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) + end end end end diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e97f602728..fb6dfff9b8 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/module/attribute_accessors' module ActionView # = Action View Form Tag Helpers @@ -17,6 +18,9 @@ module ActionView include UrlHelper include TextHelper + mattr_accessor :embed_authenticity_token_in_remote_forms + self.embed_authenticity_token_in_remote_forms = false + # Starts a form tag that points the action to an url configured with <tt>url_for_options</tt> just like # ActionController::Base#url_for. The method for the form defaults to POST. # @@ -27,7 +31,11 @@ module ActionView # 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 - # (by passing <tt>false</tt>). + # (by passing <tt>false</tt>). Remote forms may omit the embedded authenticity token + # by setting <tt>config.action_view.embed_authenticity_token_in_remote_forms = false</tt>. + # This is helpful when you're fragment-caching the form. Remote forms get the + # authenticity from the <tt>meta</tt> tag, so embedding is unnecessary unless you + # support browsers without JavaScript. # * A list of parameters to feed to the URL the form will be posted to. # * <tt>:remote</tt> - If set to true, will allow the Unobtrusive JavaScript drivers to control the # submit behavior. By default this behavior is an ajax submit. @@ -313,7 +321,7 @@ module ActionView options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end - escape = options.key?("escape") ? options.delete("escape") : true + escape = options.delete("escape") { true } content = ERB::Util.html_escape(content) if escape content_tag :textarea, content.to_s.html_safe, { "name" => name, "id" => sanitize_to_id(name) }.update(options) @@ -525,10 +533,9 @@ module ActionView # <% end %> # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> def field_set_tag(legend = nil, options = nil, &block) - content = capture(&block) output = tag(:fieldset, options, true) output.safe_concat(content_tag(:legend, legend)) unless legend.blank? - output.concat(content) + output.concat(capture(&block)) if block_given? output.safe_concat("</fieldset>") end @@ -549,6 +556,14 @@ module ActionView end alias phone_field_tag telephone_field_tag + # Creates a text field of type "date". + # + # ==== Options + # * Accepts the same options as text_field_tag. + def date_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "date")) + end + # Creates a text field of type "url". # # ==== Options @@ -609,8 +624,19 @@ module ActionView # responsibility of the caller to escape all the values. html_options["action"] = url_for(url_for_options) html_options["accept-charset"] = "UTF-8" + html_options["data-remote"] = true if html_options.delete("remote") - html_options["authenticity_token"] = html_options.delete("authenticity_token") if html_options.has_key?("authenticity_token") + + if html_options["data-remote"] && + !embed_authenticity_token_in_remote_forms && + html_options["authenticity_token"].blank? + # The authenticity token is taken from the meta tag in this case + html_options["authenticity_token"] = false + elsif html_options["authenticity_token"] == true + # Include the default authenticity_token, which is only generated when its set to nil, + # but we needed the true value to override the default of no authenticity_token on data-remote. + html_options["authenticity_token"] = nil + end end end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 309923490c..d88f5babb9 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -1,5 +1,4 @@ require 'action_view/helpers/tag_helper' -require 'active_support/core_ext/string/encoding' module ActionView module Helpers @@ -15,6 +14,8 @@ module ActionView } JS_ESCAPE_MAP["\342\200\250".force_encoding('UTF-8').encode!] = '
' + JS_ESCAPE_MAP["\342\200\251".force_encoding('UTF-8').encode!] = '
' + # Escapes carriage returns and single and double quotes for JavaScript segments. # @@ -23,7 +24,7 @@ module ActionView # $('some_element').replaceWith('<%=j render 'some/element_template' %>'); def escape_javascript(javascript) if javascript - result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } + result = javascript.gsub(/(\\|<\/|\r\n|\342\200\250|\342\200\251|[\n\r"'])/u) {|match| JS_ESCAPE_MAP[match] } javascript.html_safe? ? result.html_safe : result else '' diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index b0860f87c4..485a9357be 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -292,6 +292,7 @@ module ActionView precision = precision > 0 ? precision : 0 #don't let it be negative else rounded_number = BigDecimal.new(number.to_s).round(precision).to_f + rounded_number = rounded_number.zero? ? rounded_number.abs : rounded_number #prevent showing negative zeros end formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options) if strip_insignificant_zeros diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index ecd26891d6..71be2955a8 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -17,6 +17,10 @@ module ActionView autofocus novalidate formnovalidate open pubdate).to_set BOOLEAN_ATTRIBUTES.merge(BOOLEAN_ATTRIBUTES.map {|attribute| attribute.to_sym }) + PRE_CONTENT_STRINGS = { + :textarea => "\n" + } + # Returns an empty HTML tag of type +name+ which by default is XHTML # compliant. Set +open+ to true to create an open tag compatible # with HTML 4.0 and below. Add HTML attributes by passing an attributes @@ -126,7 +130,7 @@ module ActionView def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options content = ERB::Util.h(content) if escape - "<#{name}#{tag_options}>#{content}</#{name}>".html_safe + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe end def tag_options(options, escape = true) diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb index e874d4ca42..3cf762877f 100644 --- a/actionpack/lib/action_view/helpers/tags.rb +++ b/actionpack/lib/action_view/helpers/tags.rb @@ -4,27 +4,30 @@ module ActionView extend ActiveSupport::Autoload autoload :Base + autoload :CheckBox + autoload :CollectionCheckBoxes + autoload :CollectionRadioButtons + autoload :CollectionSelect + autoload :DateField + autoload :DateSelect + autoload :DatetimeSelect + autoload :EmailField + autoload :FileField + autoload :GroupedCollectionSelect + autoload :HiddenField autoload :Label - autoload :TextField + autoload :NumberField autoload :PasswordField - autoload :HiddenField - autoload :FileField + autoload :RadioButton + autoload :RangeField autoload :SearchField + autoload :Select autoload :TelField - autoload :UrlField - autoload :EmailField - autoload :NumberField - autoload :RangeField autoload :TextArea - autoload :CheckBox - autoload :RadioButton - autoload :Select - autoload :CollectionSelect - autoload :GroupedCollectionSelect - autoload :TimeZoneSelect - autoload :DateSelect + autoload :TextField autoload :TimeSelect - autoload :DatetimeSelect + autoload :TimeZoneSelect + autoload :UrlField end end end diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index 449f94d347..7c2f12d8e7 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -5,8 +5,6 @@ module ActionView include Helpers::ActiveModelInstanceTag, Helpers::TagHelper, Helpers::FormTagHelper include FormOptionsHelper - DEFAULT_FIELD_OPTIONS = { "size" => 30 } - attr_reader :object def initialize(object_name, method_name, template_object, options = {}) @@ -32,9 +30,11 @@ module ActionView def value_before_type_cast(object) unless object.nil? - object.respond_to?(@method_name + "_before_type_cast") ? - object.send(@method_name + "_before_type_cast") : - object.send(@method_name) + method_before_type_cast = @method_name + "_before_type_cast" + + object.respond_to?(method_before_type_cast) ? + object.send(method_before_type_cast) : + value(object) end end @@ -59,26 +59,28 @@ module ActionView end def add_default_name_and_id_for_value(tag_value, options) - unless tag_value.nil? - pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase - specified_id = options["id"] + if tag_value.nil? add_default_name_and_id(options) - options["id"] += "_#{pretty_tag_value}" if specified_id.blank? && options["id"].present? else + specified_id = options["id"] add_default_name_and_id(options) + + if specified_id.blank? && options["id"].present? + options["id"] += "_#{sanitized_value(tag_value)}" + end end end def add_default_name_and_id(options) if options.has_key?("index") - options["name"] ||= tag_name_with_index(options["index"]) + options["name"] ||= options.fetch("name"){ tag_name_with_index(options["index"]) } options["id"] = options.fetch("id"){ tag_id_with_index(options["index"]) } options.delete("index") elsif defined?(@auto_index) - options["name"] ||= tag_name_with_index(@auto_index) + options["name"] ||= options.fetch("name"){ tag_name_with_index(@auto_index) } options["id"] = options.fetch("id"){ tag_id_with_index(@auto_index) } else - options["name"] ||= tag_name + (options['multiple'] ? '[]' : '') + options["name"] ||= options.fetch("name"){ options['multiple'] ? tag_name_multiple : tag_name } options["id"] = options.fetch("id"){ tag_id } end options["id"] = [options.delete('namespace'), options["id"]].compact.join("_").presence @@ -88,6 +90,10 @@ module ActionView "#{@object_name}[#{sanitized_method_name}]" end + def tag_name_multiple + "#{tag_name}[]" + end + def tag_name_with_index(index) "#{@object_name}[#{index}][#{sanitized_method_name}]" end @@ -108,11 +114,16 @@ module ActionView @sanitized_method_name ||= @method_name.sub(/\?$/,"") end + def sanitized_value(value) + value.to_s.gsub(/\s/, "_").gsub(/[^-\w]/, "").downcase + end + def select_content_tag(option_tags, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) select = content_tag("select", add_options(option_tags, options, value(object)), html_options) - if html_options["multiple"] + + if html_options["multiple"] && options.fetch(:include_hidden) { true } tag("input", :disabled => html_options["disabled"], :name => html_options["name"], :type => "hidden", :value => "") + select else select @@ -121,13 +132,13 @@ module ActionView def add_options(option_tags, options, value = nil) if options[:include_blank] - option_tags = "<option value=\"\">#{ERB::Util.html_escape(options[:include_blank]) if options[:include_blank].kind_of?(String)}</option>\n" + option_tags + option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags end if value.blank? && options[:prompt] prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') - option_tags = "<option value=\"\">#{ERB::Util.html_escape(prompt)}</option>\n" + option_tags + option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags end - option_tags.html_safe + option_tags end end end diff --git a/actionpack/lib/action_view/helpers/tags/check_box.rb b/actionpack/lib/action_view/helpers/tags/check_box.rb index 7ad5de0596..1a4aebb936 100644 --- a/actionpack/lib/action_view/helpers/tags/check_box.rb +++ b/actionpack/lib/action_view/helpers/tags/check_box.rb @@ -25,9 +25,15 @@ module ActionView add_default_name_and_id(options) end - hidden = @unchecked_value ? tag("input", "name" => options["name"], "type" => "hidden", "value" => @unchecked_value, "disabled" => options["disabled"]) : "".html_safe + include_hidden = options.delete("include_hidden") { true } checkbox = tag("input", options) - hidden + checkbox + + if include_hidden + hidden = hidden_field_for_checkbox(options) + hidden + checkbox + else + checkbox + end end private @@ -48,6 +54,10 @@ module ActionView value.to_i != 0 end end + + def hidden_field_for_checkbox(options) + @unchecked_value ? tag("input", options.slice("name", "disabled", "form").merge!("type" => "hidden", "value" => @unchecked_value)) : "".html_safe + 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 new file mode 100644 index 0000000000..e23f5113fb --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/collection_check_boxes.rb @@ -0,0 +1,37 @@ +require 'action_view/helpers/tags/collection_helpers' + +module ActionView + module Helpers + module Tags + class CollectionCheckBoxes < Base + include CollectionHelpers + + class CheckBoxBuilder < Builder + def check_box(extra_html_options={}) + html_options = extra_html_options.merge(@input_html_options) + @template_object.check_box(@object_name, @method_name, html_options, @value, nil) + end + end + + def render + rendered_collection = render_collection do |item, value, text, default_html_options| + default_html_options[:multiple] = true + builder = instantiate_builder(CheckBoxBuilder, item, value, text, default_html_options) + + if block_given? + yield builder + else + builder.check_box + builder.label + end + end + + # Append a hidden field to make sure something will be sent back to the + # server if all check boxes are unchecked. + hidden = @template_object.hidden_field_tag(tag_name_multiple, "", :id => nil) + + rendered_collection + hidden + end + 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 new file mode 100644 index 0000000000..6a1479069f --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/collection_helpers.rb @@ -0,0 +1,82 @@ +module ActionView + module Helpers + module Tags + module CollectionHelpers + class Builder + attr_reader :object, :text, :value + + def initialize(template_object, object_name, method_name, object, + sanitized_attribute_name, text, value, input_html_options) + @template_object = template_object + @object_name = object_name + @method_name = method_name + @object = object + @sanitized_attribute_name = sanitized_attribute_name + @text = text + @value = value + @input_html_options = input_html_options + end + + def label(label_html_options={}, &block) + @template_object.label(@object_name, @sanitized_attribute_name, @text, label_html_options, &block) + end + end + + def initialize(object_name, method_name, template_object, collection, value_method, text_method, options, html_options) + @collection = collection + @value_method = value_method + @text_method = text_method + @html_options = html_options + + super(object_name, method_name, template_object, options) + end + + private + + def instantiate_builder(builder_class, item, value, text, html_options) + builder_class.new(@template_object, @object_name, @method_name, item, + sanitize_attribute_name(value), text, value, html_options) + end + + # Generate default options for collection helpers, such as :checked and + # :disabled. + def default_html_options_for_collection(item, value) #:nodoc: + html_options = @html_options.dup + + [:checked, :selected, :disabled].each do |option| + next unless current_value = @options[option] + + accept = if current_value.respond_to?(:call) + current_value.call(item) + else + Array(current_value).include?(value) + end + + if accept + html_options[option] = true + elsif option == :checked + html_options[option] = false + end + end + + html_options[:object] = @object + html_options + end + + def sanitize_attribute_name(value) #:nodoc: + "#{sanitized_method_name}_#{sanitized_value(value)}" + end + + def render_collection #:nodoc: + @collection.map do |item| + value = value_for_collection(item, @value_method) + text = value_for_collection(item, @text_method) + default_html_options = default_html_options_for_collection(item, value) + + yield item, value, text, default_html_options + end.join.html_safe + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb new file mode 100644 index 0000000000..ba2035f074 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/collection_radio_buttons.rb @@ -0,0 +1,30 @@ +require 'action_view/helpers/tags/collection_helpers' + +module ActionView + module Helpers + module Tags + class CollectionRadioButtons < Base + include CollectionHelpers + + class RadioButtonBuilder < Builder + def radio_button(extra_html_options={}) + html_options = extra_html_options.merge(@input_html_options) + @template_object.radio_button(@object_name, @method_name, @value, html_options) + end + end + + def render + render_collection do |item, value, text, default_html_options| + builder = instantiate_builder(RadioButtonBuilder, item, value, text, default_html_options) + + if block_given? + yield builder + else + builder.radio_button + builder.label + end + end + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb new file mode 100644 index 0000000000..bb968e9f39 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/date_field.rb @@ -0,0 +1,15 @@ +module ActionView + module Helpers + module Tags + class DateField < TextField #:nodoc: + def render + options = @options.stringify_keys + options["value"] = @options.fetch("value") { value(object).try(:to_date) } + options["size"] = nil + @options = options + super + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb index 507466a57a..507ba8835f 100644 --- a/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb +++ b/actionpack/lib/action_view/helpers/tags/grouped_collection_select.rb @@ -14,8 +14,13 @@ module ActionView end def render + option_tags_options = { + :selected => @options.fetch(:selected) { value(@object) }, + :disabled => @options[:disabled] + } + select_content_tag( - option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, value(@object)), @options, @html_options + option_groups_from_collection_for_select(@collection, @group_method, @group_label_method, @option_key_method, @option_value_method, option_tags_options), @options, @html_options ) end end diff --git a/actionpack/lib/action_view/helpers/tags/label.rb b/actionpack/lib/action_view/helpers/tags/label.rb index 1bd71c2778..1c8bf063ea 100644 --- a/actionpack/lib/action_view/helpers/tags/label.rb +++ b/actionpack/lib/action_view/helpers/tags/label.rb @@ -3,16 +3,16 @@ module ActionView module Tags class Label < Base #:nodoc: def initialize(object_name, method_name, template_object, content_or_options = nil, options = nil) + options ||= {} + content_is_options = content_or_options.is_a?(Hash) if content_is_options - options = content_or_options + options.merge! content_or_options @content = nil else @content = content_or_options end - options ||= {} - super(object_name, method_name, template_object, options) end diff --git a/actionpack/lib/action_view/helpers/tags/text_area.rb b/actionpack/lib/action_view/helpers/tags/text_area.rb index a7db8eb437..f74652c5e7 100644 --- a/actionpack/lib/action_view/helpers/tags/text_area.rb +++ b/actionpack/lib/action_view/helpers/tags/text_area.rb @@ -2,17 +2,15 @@ module ActionView module Helpers module Tags class TextArea < Base #:nodoc: - DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 } - def render - options = DEFAULT_TEXT_AREA_OPTIONS.merge(@options.stringify_keys) + options = @options.stringify_keys add_default_name_and_id(options) if size = options.delete("size") options["cols"], options["rows"] = size.split("x") if size.respond_to?(:split) end - content_tag("textarea", ERB::Util.html_escape(options.delete('value') || value_before_type_cast(object)), options) + content_tag("textarea", options.delete('value') || value_before_type_cast(object), options) end end end diff --git a/actionpack/lib/action_view/helpers/tags/text_field.rb b/actionpack/lib/action_view/helpers/tags/text_field.rb index ce5182d20f..024a1a8af2 100644 --- a/actionpack/lib/action_view/helpers/tags/text_field.rb +++ b/actionpack/lib/action_view/helpers/tags/text_field.rb @@ -4,8 +4,7 @@ module ActionView class TextField < Base #:nodoc: def render options = @options.stringify_keys - options["size"] = options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"] unless options.key?("size") - options = DEFAULT_FIELD_OPTIONS.merge(options) + options["size"] = options["maxlength"] unless options.key?("size") options["type"] ||= field_type options["value"] = options.fetch("value"){ value_before_type_cast(object) } unless field_type == "file" options["value"] &&= ERB::Util.html_escape(options["value"]) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index ce79a3da48..3dc651501e 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -90,11 +90,11 @@ module ActionView # Highlights one or more +phrases+ everywhere in +text+ by inserting it into # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt> # as a single-quoted string with \1 where the phrase is to be inserted (defaults to - # '<strong class="highlight">\1</strong>') + # '<mark>\1</mark>') # # ==== Examples # highlight('You searched for: rails', 'rails') - # # => You searched for: <strong class="highlight">rails</strong> + # # => You searched for: <mark>rails</mark> # # highlight('You searched for: ruby, rails, dhh', 'actionpack') # # => You searched for: ruby, rails, dhh @@ -111,9 +111,9 @@ module ActionView def highlight(text, phrases, *args) options = args.extract_options! unless args.empty? - options[:highlighter] = args[0] || '<strong class="highlight">\1</strong>' + options[:highlighter] = args[0] || '<mark>\1</mark>' end - options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>') + options.reverse_merge!(:highlighter => '<mark>\1</mark>') text = sanitize(text) unless options[:sanitize] == false if text.blank? || phrases.blank? diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index b5fc882e31..4a641fada3 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -23,20 +23,25 @@ module ActionView include ActionDispatch::Routing::UrlFor include TagHelper - def _routes_context - controller - end + # We need to override url_optoins, _routes_context + # and optimize_routes_generation? to consider the controller. - # Need to map default url options to controller one. - # def default_url_options(*args) #:nodoc: - # controller.send(:default_url_options, *args) - # end - # - def url_options + def url_options #:nodoc: return super unless controller.respond_to?(:url_options) controller.url_options end + def _routes_context #:nodoc: + controller + end + protected :_routes_context + + def optimize_routes_generation? #:nodoc: + controller.respond_to?(:optimize_routes_generation?) ? + controller.optimize_routes_generation? : super + end + protected :optimize_routes_generation? + # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default @@ -55,7 +60,7 @@ module ActionView # # ==== Relying on named routes # - # Passing a record (like an Active Record or Active Resource) instead of a Hash as the options parameter will + # Passing a record (like an Active Record) instead of a Hash as the options parameter will # trigger the named route for that record. The lookup will happen on the name of the class. So passing a # Workshop object will attempt to use the +workshop_path+ route. If you have a nested route, such as # +admin_workshop_path+ you'll have to call that explicitly (it's impossible for +url_for+ to guess that route). @@ -146,12 +151,12 @@ module ActionView # create an HTML form and immediately submit the form for processing using # the HTTP verb specified. Useful for having links perform a POST operation # in dangerous actions like deleting a record (which search bots can follow - # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt> and <tt>:put</tt>. + # while spidering your site). Supported verbs are <tt>:post</tt>, <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. # Note that if the user has JavaScript disabled, the request will fall back # to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript # disabled clicking the link will have no effect. If you are relying on the # POST behavior, you should check for it in your controller's action by using - # the request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>. + # the request object's methods for <tt>post?</tt>, <tt>delete?</tt>, <tt>:patch</tt>, or <tt>put?</tt>. # * <tt>:remote => true</tt> - This will allow the unobtrusive JavaScript # driver to make an Ajax request to the URL in question instead of following # the link. The drivers each provide mechanisms for listening for the @@ -272,7 +277,7 @@ module ActionView # # There are a few special +html_options+: # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>, - # <tt>:delete</tt> and <tt>:put</tt>. By default it will be <tt>:post</tt>. + # <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>. # * <tt>:disabled</tt> - If set to true, it will generate a disabled button. # * <tt>:confirm</tt> - This will use the unobtrusive JavaScript driver to # prompt with the question specified. If the user accepts, the link is @@ -329,7 +334,7 @@ module ActionView remote = html_options.delete('remote') method = html_options.delete('method').to_s - method_tag = %w{put delete}.include?(method) ? method_tag(method) : "" + method_tag = %w{patch put delete}.include?(method) ? method_tag(method) : ''.html_safe form_method = method == 'get' ? 'get' : 'post' form_options = html_options.delete('form') || {} @@ -342,7 +347,8 @@ module ActionView html_options = convert_options_to_data_attributes(options, html_options) html_options.merge!("type" => "submit", "value" => name || url) - "#{tag(:form, form_options, true)}<div>#{method_tag}#{tag("input", html_options)}#{request_token_tag}</div></form>".html_safe + inner_tags = method_tag.safe_concat tag('input', html_options).safe_concat request_token_tag + content_tag('form', content_tag('div', inner_tags), form_options) end diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index 7cca7d969a..8e9db634fb 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -147,15 +147,8 @@ # Default value for :prompt => true in FormOptionsHelper prompt: "Please select" - # Default translation keys for submit FormHelper + # Default translation keys for submit and button FormHelper submit: create: 'Create %{model}' update: 'Update %{model}' submit: 'Save %{model}' - - # Default translation keys for button FormHelper - button: - create: 'Create %{model}' - update: 'Update %{model}' - submit: 'Save %{model}' - diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 90c4a2759a..b7945a23be 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -9,7 +9,7 @@ module ActionView # generate a key, given to view paths, used in the resolver cache lookup. Since # this key is generated just once during the request, it speeds up all cache accesses. class LookupContext #:nodoc: - attr_accessor :prefixes + attr_accessor :prefixes, :rendered_format mattr_accessor :fallbacks @@fallbacks = FallbackFileSystemResolver.instances @@ -170,23 +170,15 @@ module ActionView def initialize(view_paths, details = {}, prefixes = []) @details, @details_key = {}, nil - @frozen_formats, @skip_default_locale = false, false + @skip_default_locale = false @cache = true @prefixes = prefixes + @rendered_format = nil self.view_paths = view_paths initialize_details(details) end - # Freeze the current formats in the lookup context. By freezing them, you - # that next template lookups are not going to modify the formats. The con - # use this, to ensure that formats won't be further modified (as it does - def freeze_formats(formats, unless_frozen=false) #:nodoc: - return if unless_frozen && @frozen_formats - self.formats = formats - @frozen_formats = true - end - # Override formats= to expand ["*/*"] values and automatically # add :html as fallback to :js. def formats=(values) diff --git a/actionpack/lib/action_view/railtie.rb b/actionpack/lib/action_view/railtie.rb index 43371a1c49..9f5e3be454 100644 --- a/actionpack/lib/action_view/railtie.rb +++ b/actionpack/lib/action_view/railtie.rb @@ -7,6 +7,14 @@ module ActionView config.action_view = ActiveSupport::OrderedOptions.new config.action_view.stylesheet_expansions = {} config.action_view.javascript_expansions = { :defaults => %w(jquery jquery_ujs) } + config.action_view.embed_authenticity_token_in_remote_forms = false + + initializer "action_view.embed_authenticity_token_in_remote_forms" do |app| + ActiveSupport.on_load(:action_view) do + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = + app.config.action_view.delete(:embed_authenticity_token_in_remote_forms) + end + end initializer "action_view.logger" do ActiveSupport.on_load(:action_view) { self.logger ||= Rails.logger } diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index a588abcee3..52473cd222 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -1,7 +1,7 @@ module ActionView class AbstractRenderer #:nodoc: delegate :find_template, :template_exists?, :with_fallbacks, :update_details, - :with_layout_format, :formats, :freeze_formats, :to => :@lookup_context + :with_layout_format, :formats, :to => :@lookup_context def initialize(lookup_context) @lookup_context = lookup_context diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 3033294883..d4c652c0d1 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -32,7 +32,7 @@ module ActionView # # <%= render :partial => "account", :object => @buyer %> # - # would provide the +@buyer+ object to the partial, available under the local variable +account+ and is + # would provide the <tt>@buyer</tt> object to the partial, available under the local variable +account+ and is # equivalent to: # # <%= render :partial => "account", :locals => { :account => @buyer } %> @@ -158,6 +158,43 @@ module ActionView # Name: <%= user.name %> # </div> # + # If a collection is given, the layout will be rendered once for each item in the collection. Just think + # these two snippets have the same output: + # + # <%# app/views/users/_user.html.erb %> + # Name: <%= user.name %> + # + # <%# app/views/users/index.html.erb %> + # <%# This does not use layouts %> + # <ul> + # <% users.each do |user| -%> + # <li> + # <%= render :partial => "user", :locals => { :user => user } %> + # </li> + # <% end -%> + # </ul> + # + # <%# app/views/users/_li_layout.html.erb %> + # <li> + # <%= yield %> + # </li> + # + # <%# app/views/users/index.html.erb %> + # <ul> + # <%= render :partial => "user", :layout => "li_layout", :collection => users %> + # </ul> + # + # Given two users whose names are Alice and Bob, these snippets return: + # + # <ul> + # <li> + # Name: Alice + # </li> + # <li> + # Name: Bob + # </li> + # </ul> + # # You can also apply a layout to a block within any template: # # <%# app/views/users/_chief.html.erb &> @@ -220,6 +257,14 @@ module ActionView setup(context, options, block) identifier = (@template = find_partial) ? @template.identifier : @path + @lookup_context.rendered_format ||= begin + if @template && @template.formats.present? + @template.formats.first + else + formats.first + end + end + if @collection instrument(:collection, :identifier => identifier || "collection", :count => @collection.size) do render_collection @@ -238,7 +283,14 @@ module ActionView spacer = find_template(@options[:spacer_template]).render(@view, @locals) end + if layout = @options[:layout] + layout = find_template(layout) + end + result = @template ? collection_with_template : collection_without_template + + result.map!{|content| layout.render(@view, @locals) { content } } if layout + result.join(spacer).html_safe end @@ -340,9 +392,10 @@ module ActionView locals[as] = object segments << template.render(@view, locals) end - + segments end + def collection_without_template segments, locals, collection_data = [], @locals, @collection_data diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index f3abc6d533..ae923de24e 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -6,7 +6,13 @@ module ActionView @view = context @details = extract_details(options) template = determine_template(options) - freeze_formats(template.formats, true) + context = @lookup_context + + unless context.rendered_format + context.formats = template.formats unless template.formats.empty? + context.rendered_format = context.formats.first + end + render_template(template, options[:layout], options[:locals]) end diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb index aa693335e3..4e22bec6cc 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionpack/lib/action_view/template/handlers.rb @@ -17,15 +17,13 @@ module ActionView #:nodoc: @@template_extensions ||= @@template_handlers.keys end - # Register a class that knows how to handle template files with the given + # Register an object that knows how to handle template files with the given # extension. This can be used to implement new template types. - # The constructor for the class must take the ActiveView::Base instance - # as a parameter, and the class must implement a +render+ method that - # takes the contents of the template to render as well as the Hash of - # local assigns available to the template. The +render+ method ought to - # return the rendered template as a string. - def register_template_handler(extension, klass) - @@template_handlers[extension.to_sym] = klass + # The handler must respond to `:call`, which will be passed the template + # and should return the rendered template as a String. + def register_template_handler(extension, handler) + @@template_handlers[extension.to_sym] = handler + @@template_extensions = nil end def template_handler_extensions diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 323df67c97..19b9112afd 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -44,10 +44,6 @@ module ActionView class_attribute :erb_trim_mode self.erb_trim_mode = '-' - # Default format used by ERB. - class_attribute :default_format - self.default_format = Mime::HTML - # Default implementation used. class_attribute :erb_implementation self.erb_implementation = Erubis diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 7fa86866a7..8ea2e5bfe4 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -176,7 +176,7 @@ module ActionView end end - # A resolver that loads files from the filesystem. It allows to set your own + # A resolver that loads files from the filesystem. It allows setting your own # resolving pattern. Such pattern can be a glob string supported by some variables. # # ==== Examples @@ -192,7 +192,7 @@ module ActionView # # FileSystemResolver.new("/path/to/views", ":prefix/{:formats/,}:action{.:locale,}{.:formats,}{.:handlers,}") # - # If you don't specify pattern then the default will be used. + # If you don't specify a pattern then the default will be used. # # In order to use any of the customized resolvers above in a Rails application, you just need # to configure ActionController::Base.view_paths in an initializer, for example: @@ -204,10 +204,10 @@ module ActionView # # ==== Pattern format and variables # - # Pattern have to be a valid glob string, and it allows you to use the + # Pattern has to be a valid glob string, and it allows you to use the # following variables: # - # * <tt>:prefix</tt> - usualy the controller path + # * <tt>:prefix</tt> - usually the controller path # * <tt>:action</tt> - name of the action # * <tt>:locale</tt> - possible locale versions # * <tt>:formats</tt> - possible request formats (for example html, json, xml...) diff --git a/actionpack/lib/sprockets/assets.rake b/actionpack/lib/sprockets/assets.rake deleted file mode 100644 index 2e92fe416b..0000000000 --- a/actionpack/lib/sprockets/assets.rake +++ /dev/null @@ -1,105 +0,0 @@ -require "fileutils" - -namespace :assets do - def ruby_rake_task(task, fork = true) - env = ENV['RAILS_ENV'] || 'production' - groups = ENV['RAILS_GROUPS'] || 'assets' - args = [$0, task,"RAILS_ENV=#{env}","RAILS_GROUPS=#{groups}"] - args << "--trace" if Rake.application.options.trace - if $0 =~ /rake\.bat\Z/i - Kernel.exec $0, *args - else - fork ? ruby(*args) : Kernel.exec(FileUtils::RUBY, *args) - end - end - - # We are currently running with no explicit bundler group - # and/or no explicit environment - we have to reinvoke rake to - # execute this task. - def invoke_or_reboot_rake_task(task) - if ENV['RAILS_GROUPS'].to_s.empty? || ENV['RAILS_ENV'].to_s.empty? - ruby_rake_task task - else - Rake::Task[task].invoke - end - end - - desc "Compile all the assets named in config.assets.precompile" - task :precompile do - invoke_or_reboot_rake_task "assets:precompile:all" - end - - namespace :precompile do - def internal_precompile(digest=nil) - unless Rails.application.config.assets.enabled - warn "Cannot precompile assets if sprockets is disabled. Please set config.assets.enabled to true" - exit - end - - # Ensure that action view is loaded and the appropriate - # sprockets hooks get executed - _ = ActionView::Base - - config = Rails.application.config - config.assets.compile = true - config.assets.digest = digest unless digest.nil? - config.assets.digests = {} - - env = Rails.application.assets - target = File.join(Rails.public_path, config.assets.prefix) - compiler = Sprockets::StaticCompiler.new(env, - target, - config.assets.precompile, - :manifest_path => config.assets.manifest, - :digest => config.assets.digest, - :manifest => digest.nil?) - compiler.compile - end - - task :all do - Rake::Task["assets:precompile:primary"].invoke - # We need to reinvoke in order to run the secondary digestless - # asset compilation run - a fresh Sprockets environment is - # required in order to compile digestless assets as the - # environment has already cached the assets on the primary - # run. - ruby_rake_task("assets:precompile:nondigest", false) if Rails.application.config.assets.digest - end - - task :primary => ["assets:cache:clean"] do - internal_precompile - end - - task :nondigest => ["assets:cache:clean"] do - internal_precompile(false) - end - end - - desc "Remove compiled assets" - task :clean do - invoke_or_reboot_rake_task "assets:clean:all" - end - - namespace :clean do - task :all => ["assets:cache:clean"] do - config = Rails.application.config - public_asset_path = File.join(Rails.public_path, config.assets.prefix) - rm_rf public_asset_path, :secure => true - end - end - - namespace :cache do - task :clean => ["assets:environment"] do - Rails.application.assets.cache.clear - end - end - - task :environment do - if Rails.application.config.assets.initialize_on_precompile - Rake::Task["environment"].invoke - else - Rails.application.initialize!(:assets) - Sprockets::Bootstrap.new(Rails.application).run - end - end -end diff --git a/actionpack/lib/sprockets/bootstrap.rb b/actionpack/lib/sprockets/bootstrap.rb deleted file mode 100644 index 395b264fe7..0000000000 --- a/actionpack/lib/sprockets/bootstrap.rb +++ /dev/null @@ -1,37 +0,0 @@ -module Sprockets - class Bootstrap - def initialize(app) - @app = app - end - - # TODO: Get rid of config.assets.enabled - def run - app, config = @app, @app.config - return unless app.assets - - config.assets.paths.each { |path| app.assets.append_path(path) } - - if config.assets.compress - # temporarily hardcode default JS compressor to uglify. Soon, it will work - # the same as SCSS, where a default plugin sets the default. - unless config.assets.js_compressor == false - app.assets.js_compressor = LazyCompressor.new { Sprockets::Compressors.registered_js_compressor(config.assets.js_compressor || :uglifier) } - end - - unless config.assets.css_compressor == false - app.assets.css_compressor = LazyCompressor.new { Sprockets::Compressors.registered_css_compressor(config.assets.css_compressor) } - end - end - - if config.assets.compile - app.routes.prepend do - mount app.assets => config.assets.prefix - end - end - - if config.assets.digest - app.assets = app.assets.index - end - end - end -end diff --git a/actionpack/lib/sprockets/compressors.rb b/actionpack/lib/sprockets/compressors.rb deleted file mode 100644 index cb3e13314b..0000000000 --- a/actionpack/lib/sprockets/compressors.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Sprockets - module Compressors - @@css_compressors = {} - @@js_compressors = {} - @@default_css_compressor = nil - @@default_js_compressor = nil - - def self.register_css_compressor(name, klass, options = {}) - @@default_css_compressor = name.to_sym if options[:default] || @@default_css_compressor.nil? - @@css_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]} - end - - def self.register_js_compressor(name, klass, options = {}) - @@default_js_compressor = name.to_sym if options[:default] || @@default_js_compressor.nil? - @@js_compressors[name.to_sym] = {:klass => klass.to_s, :require => options[:require]} - end - - def self.registered_css_compressor(name) - if name.respond_to?(:to_sym) - compressor = @@css_compressors[name.to_sym] || @@css_compressors[@@default_css_compressor] - require compressor[:require] if compressor[:require] - compressor[:klass].constantize.new - else - name - end - end - - def self.registered_js_compressor(name) - if name.respond_to?(:to_sym) - compressor = @@js_compressors[name.to_sym] || @@js_compressors[@@default_js_compressor] - require compressor[:require] if compressor[:require] - compressor[:klass].constantize.new - else - name - end - end - - # The default compressors must be registered in default plugins (ex. Sass-Rails) - register_css_compressor(:scss, 'Sass::Rails::Compressor', :require => 'sass/rails/compressor', :default => true) - register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier', :default => true) - - # Automaticaly register some compressors - register_css_compressor(:yui, 'YUI::CssCompressor', :require => 'yui/compressor') - register_js_compressor(:closure, 'Closure::Compiler', :require => 'closure-compiler') - register_js_compressor(:yui, 'YUI::JavaScriptCompressor', :require => 'yui/compressor') - end - - # An asset compressor which does nothing. - # - # This compressor simply returns the asset as-is, without any compression - # whatsoever. It is useful in development mode, when compression isn't - # needed but using the same asset pipeline as production is desired. - class NullCompressor #:nodoc: - def compress(content) - content - end - end - - # An asset compressor which only initializes the underlying compression - # engine when needed. - # - # This postpones the initialization of the compressor until - # <code>#compress</code> is called the first time. - class LazyCompressor #:nodoc: - # Initializes a new LazyCompressor. - # - # The block should return a compressor when called, i.e. an object - # which responds to <code>#compress</code>. - def initialize(&block) - @block = block - end - - def compress(content) - compressor.compress(content) - end - - private - - def compressor - @compressor ||= (@block.call || NullCompressor.new) - end - end -end diff --git a/actionpack/lib/sprockets/helpers.rb b/actionpack/lib/sprockets/helpers.rb deleted file mode 100644 index fee48386e0..0000000000 --- a/actionpack/lib/sprockets/helpers.rb +++ /dev/null @@ -1,6 +0,0 @@ -module Sprockets - module Helpers - autoload :RailsHelper, "sprockets/helpers/rails_helper" - autoload :IsolatedHelper, "sprockets/helpers/isolated_helper" - end -end diff --git a/actionpack/lib/sprockets/helpers/isolated_helper.rb b/actionpack/lib/sprockets/helpers/isolated_helper.rb deleted file mode 100644 index 3adb928c45..0000000000 --- a/actionpack/lib/sprockets/helpers/isolated_helper.rb +++ /dev/null @@ -1,13 +0,0 @@ -module Sprockets - module Helpers - module IsolatedHelper - def controller - nil - end - - def config - Rails.application.config.action_controller - end - end - end -end diff --git a/actionpack/lib/sprockets/helpers/rails_helper.rb b/actionpack/lib/sprockets/helpers/rails_helper.rb deleted file mode 100644 index 976ae5a76d..0000000000 --- a/actionpack/lib/sprockets/helpers/rails_helper.rb +++ /dev/null @@ -1,167 +0,0 @@ -require "action_view" - -module Sprockets - module Helpers - module RailsHelper - extend ActiveSupport::Concern - include ActionView::Helpers::AssetTagHelper - - def asset_paths - @asset_paths ||= begin - paths = RailsHelper::AssetPaths.new(config, controller) - paths.asset_environment = asset_environment - paths.asset_digests = asset_digests - paths.compile_assets = compile_assets? - paths.digest_assets = digest_assets? - paths - end - end - - def javascript_include_tag(*sources) - options = sources.extract_options! - debug = options.key?(:debug) ? options.delete(:debug) : debug_assets? - body = options.key?(:body) ? options.delete(:body) : false - digest = options.key?(:digest) ? options.delete(:digest) : digest_assets? - - sources.collect do |source| - if debug && asset = asset_paths.asset_for(source, 'js') - asset.to_a.map { |dep| - super(dep.pathname.to_s, { :src => path_to_asset(dep, :ext => 'js', :body => true, :digest => digest) }.merge!(options)) - } - else - super(source.to_s, { :src => path_to_asset(source, :ext => 'js', :body => body, :digest => digest) }.merge!(options)) - end - end.join("\n").html_safe - end - - def stylesheet_link_tag(*sources) - options = sources.extract_options! - debug = options.key?(:debug) ? options.delete(:debug) : debug_assets? - body = options.key?(:body) ? options.delete(:body) : false - digest = options.key?(:digest) ? options.delete(:digest) : digest_assets? - - sources.collect do |source| - if debug && asset = asset_paths.asset_for(source, 'css') - asset.to_a.map { |dep| - super(dep.pathname.to_s, { :href => path_to_asset(dep, :ext => 'css', :body => true, :protocol => :request, :digest => digest) }.merge!(options)) - } - else - super(source.to_s, { :href => path_to_asset(source, :ext => 'css', :body => body, :protocol => :request, :digest => digest) }.merge!(options)) - end - end.join("\n").html_safe - end - - def asset_path(source, options = {}) - source = source.logical_path if source.respond_to?(:logical_path) - path = asset_paths.compute_public_path(source, asset_prefix, options.merge(:body => true)) - options[:body] ? "#{path}?body=1" : path - end - alias_method :path_to_asset, :asset_path # aliased to avoid conflicts with an asset_path named route - - def image_path(source) - path_to_asset(source) - end - alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route - - def font_path(source) - path_to_asset(source) - end - alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route - - def javascript_path(source) - path_to_asset(source, :ext => 'js') - end - alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with an javascript_path named route - - def stylesheet_path(source) - path_to_asset(source, :ext => 'css') - end - alias_method :path_to_stylesheet, :stylesheet_path # aliased to avoid conflicts with an stylesheet_path named route - - private - def debug_assets? - compile_assets? && (Rails.application.config.assets.debug || params[:debug_assets]) - rescue NoMethodError - false - end - - # Override to specify an alternative prefix for asset path generation. - # When combined with a custom +asset_environment+, this can be used to - # implement themes that can take advantage of the asset pipeline. - # - # If you only want to change where the assets are mounted, refer to - # +config.assets.prefix+ instead. - def asset_prefix - Rails.application.config.assets.prefix - end - - def asset_digests - Rails.application.config.assets.digests - end - - def compile_assets? - Rails.application.config.assets.compile - end - - def digest_assets? - Rails.application.config.assets.digest - end - - # Override to specify an alternative asset environment for asset - # path generation. The environment should already have been mounted - # at the prefix returned by +asset_prefix+. - def asset_environment - Rails.application.assets - end - - class AssetPaths < ::ActionView::AssetPaths #:nodoc: - attr_accessor :asset_environment, :asset_prefix, :asset_digests, :compile_assets, :digest_assets - - class AssetNotPrecompiledError < StandardError; end - - def asset_for(source, ext) - source = source.to_s - return nil if is_uri?(source) - source = rewrite_extension(source, nil, ext) - asset_environment[source] - rescue Sprockets::FileOutsidePaths - nil - end - - def digest_for(logical_path) - if digest_assets && asset_digests && (digest = asset_digests[logical_path]) - return digest - end - - if compile_assets - if digest_assets && asset = asset_environment[logical_path] - return asset.digest_path - end - return logical_path - else - raise AssetNotPrecompiledError.new("#{logical_path} isn't precompiled") - end - end - - def rewrite_asset_path(source, dir, options = {}) - if source[0] == ?/ - source - else - source = digest_for(source) unless options[:digest] == false - source = File.join(dir, source) - source = "/#{source}" unless source =~ /^\// - source - end - end - - def rewrite_extension(source, dir, ext) - if ext && File.extname(source) != ".#{ext}" - "#{source}.#{ext}" - else - source - end - end - end - end - end -end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb deleted file mode 100644 index 44ddab0950..0000000000 --- a/actionpack/lib/sprockets/railtie.rb +++ /dev/null @@ -1,62 +0,0 @@ -require "action_controller/railtie" - -module Sprockets - autoload :Bootstrap, "sprockets/bootstrap" - autoload :Helpers, "sprockets/helpers" - autoload :Compressors, "sprockets/compressors" - autoload :LazyCompressor, "sprockets/compressors" - autoload :NullCompressor, "sprockets/compressors" - autoload :StaticCompiler, "sprockets/static_compiler" - - # TODO: Get rid of config.assets.enabled - class Railtie < ::Rails::Railtie - rake_tasks do - load "sprockets/assets.rake" - end - - initializer "sprockets.environment", :group => :all do |app| - config = app.config - next unless config.assets.enabled - - require 'sprockets' - - app.assets = Sprockets::Environment.new(app.root.to_s) do |env| - env.version = ::Rails.env + "-#{config.assets.version}" - - if config.assets.logger != false - env.logger = config.assets.logger || ::Rails.logger - end - - if config.assets.cache_store != false - env.cache = ActiveSupport::Cache.lookup_store(config.assets.cache_store) || ::Rails.cache - end - end - - if config.assets.manifest - path = File.join(config.assets.manifest, "manifest.yml") - else - path = File.join(Rails.public_path, config.assets.prefix, "manifest.yml") - end - - if File.exist?(path) - config.assets.digests = YAML.load_file(path) - end - - ActiveSupport.on_load(:action_view) do - include ::Sprockets::Helpers::RailsHelper - app.assets.context_class.instance_eval do - include ::Sprockets::Helpers::IsolatedHelper - include ::Sprockets::Helpers::RailsHelper - end - end - end - - # We need to configure this after initialization to ensure we collect - # paths from all engines. This hook is invoked exactly before routes - # are compiled, and so that other Railties have an opportunity to - # register compressors. - config.after_initialize do |app| - Sprockets::Bootstrap.new(app).run - end - end -end diff --git a/actionpack/lib/sprockets/static_compiler.rb b/actionpack/lib/sprockets/static_compiler.rb deleted file mode 100644 index 719df0bd51..0000000000 --- a/actionpack/lib/sprockets/static_compiler.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'fileutils' - -module Sprockets - class StaticCompiler - attr_accessor :env, :target, :paths - - def initialize(env, target, paths, options = {}) - @env = env - @target = target - @paths = paths - @digest = options.key?(:digest) ? options.delete(:digest) : true - @manifest = options.key?(:manifest) ? options.delete(:manifest) : true - @manifest_path = options.delete(:manifest_path) || target - @zip_files = options.delete(:zip_files) || /\.(?:css|html|js|svg|txt|xml)$/ - end - - def compile - manifest = {} - env.each_logical_path do |logical_path| - next unless compile_path?(logical_path) - if asset = env.find_asset(logical_path) - manifest[logical_path] = write_asset(asset) - end - end - write_manifest(manifest) if @manifest - end - - def write_manifest(manifest) - FileUtils.mkdir_p(@manifest_path) - File.open("#{@manifest_path}/manifest.yml", 'wb') do |f| - YAML.dump(manifest, f) - end - end - - def write_asset(asset) - path_for(asset).tap do |path| - filename = File.join(target, path) - FileUtils.mkdir_p File.dirname(filename) - asset.write_to(filename) - asset.write_to("#{filename}.gz") if filename.to_s =~ @zip_files - end - end - - def compile_path?(logical_path) - paths.each do |path| - case path - when Regexp - return true if path.match(logical_path) - when Proc - return true if path.call(logical_path) - else - return true if File.fnmatch(path.to_s, logical_path) - end - end - false - end - - def path_for(asset) - @digest ? asset.digest_path : asset.logical_path - end - end -end diff --git a/actionpack/test/abstract/abstract_controller_test.rb b/actionpack/test/abstract/abstract_controller_test.rb index bf068aedcd..62f82a4c7a 100644 --- a/actionpack/test/abstract/abstract_controller_test.rb +++ b/actionpack/test/abstract/abstract_controller_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'set' module AbstractController module Testing @@ -28,7 +29,7 @@ module AbstractController # Test Render mixin # ==== class RenderingController < AbstractController::Base - include ::AbstractController::Rendering + include AbstractController::Rendering def _prefixes [] @@ -152,7 +153,7 @@ module AbstractController # ==== # self._layout is used when defined class WithLayouts < PrefixedViews - include Layouts + include AbstractController::Layouts private def self.layout(formats) @@ -254,7 +255,7 @@ module AbstractController class TestActionMethodsReloading < ActiveSupport::TestCase test "action_methods should be reloaded after defining a new method" do - assert_equal ["index"], Me6.action_methods + assert_equal Set.new(["index"]), Me6.action_methods end end diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index 2ebcebbbb7..c14d24905b 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' module AbstractController module Testing class MyCollector - include Collector + include AbstractController::Collector attr_accessor :responses def initialize @@ -54,4 +54,4 @@ module AbstractController end end end -end
\ No newline at end of file +end diff --git a/actionpack/test/abstract/helper_test.rb b/actionpack/test/abstract/helper_test.rb index b28a5b5afb..9a7445de7b 100644 --- a/actionpack/test/abstract/helper_test.rb +++ b/actionpack/test/abstract/helper_test.rb @@ -7,7 +7,7 @@ module AbstractController class ControllerWithHelpers < AbstractController::Base include AbstractController::Rendering - include Helpers + include AbstractController::Helpers def with_module render :inline => "Module <%= included_method %>" @@ -44,7 +44,7 @@ module AbstractController class AbstractHelpersBlock < ControllerWithHelpers helper do - include ::AbstractController::Testing::HelperyTest + include AbstractController::Testing::HelperyTest end end diff --git a/actionpack/test/abstract/layouts_test.rb b/actionpack/test/abstract/layouts_test.rb index fc25718d9e..58795aa327 100644 --- a/actionpack/test/abstract/layouts_test.rb +++ b/actionpack/test/abstract/layouts_test.rb @@ -72,6 +72,27 @@ module AbstractControllerTests end end + class WithZeroArityProc < Base + layout proc { "overwrite" } + + def index + render :template => ActionView::Template::Text.new("Hello zero arity proc!") + end + end + + class WithProcInContextOfInstance < Base + def an_instance_method; end + + layout proc { + break unless respond_to? :an_instance_method + "overwrite" + } + + def index + render :template => ActionView::Template::Text.new("Hello again zero arity proc!") + end + end + class WithSymbol < Base layout :hello @@ -221,6 +242,18 @@ module AbstractControllerTests assert_equal "Overwrite Hello proc!", controller.response_body end + test "when layout is specified as a proc without parameters it works just the same" do + controller = WithZeroArityProc.new + controller.process(:index) + assert_equal "Overwrite Hello zero arity proc!", controller.response_body + end + + test "when layout is specified as a proc without parameters the block is evaluated in the context of an instance" do + controller = WithProcInContextOfInstance.new + controller.process(:index) + assert_equal "Overwrite Hello again zero arity proc!", controller.response_body + end + test "when layout is specified as a symbol, call the requested method and use the layout returned" do controller = WithSymbol.new controller.process(:index) @@ -299,6 +332,18 @@ module AbstractControllerTests controller.process(:index) assert_equal "Overwrite Hello index!", controller.response_body end + + test "layout for anonymous controller" do + klass = Class.new(WithString) do + def index + render :text => 'index', :layout => true + end + end + + controller = klass.new + controller.process(:index) + assert_equal "With String index", controller.response_body + end end end end diff --git a/actionpack/test/controller/addresses_render_test.rb b/actionpack/test/controller/addresses_render_test.rb deleted file mode 100644 index 07f27fd362..0000000000 --- a/actionpack/test/controller/addresses_render_test.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'abstract_unit' -require 'active_support/logger' -require 'controller/fake_controllers' - -class Address - class << self - def count(conditions = nil, join = nil) - nil - end - - def find_all(arg1, arg2, arg3, arg4) - [] - end - - def find(*args) - [] - end - end -end - -class AddressesTest < ActionController::TestCase - tests AddressesController - - def setup - super - # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get - # a more accurate simulation of what happens in "real life". - @controller.logger = ActiveSupport::Logger.new(nil) - - @request.host = "www.nextangle.com" - end - - def test_list - get :list - assert_equal "We only need to get this far!", @response.body.chomp - end -end diff --git a/actionpack/test/controller/base_test.rb b/actionpack/test/controller/base_test.rb index 70e03d24ea..2d4083252e 100644 --- a/actionpack/test/controller/base_test.rb +++ b/actionpack/test/controller/base_test.rb @@ -56,7 +56,7 @@ class UrlOptionsController < ActionController::Base end def url_options - super.merge(:host => 'www.override.com', :action => 'new', :locale => 'en') + super.merge(:host => 'www.override.com') end end @@ -130,8 +130,6 @@ class PerformActionTest < ActionController::TestCase @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new @request.host = "www.nextangle.com" - - rescue_action_in_public! end def test_process_should_be_precise @@ -155,7 +153,22 @@ class UrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! + end + + def test_url_for_query_params_included + rs = ActionDispatch::Routing::RouteSet.new + rs.draw do + match 'home' => 'pages#home' + end + + options = { + :action => "home", + :controller => "pages", + :only_path => true, + :params => { "token" => "secret" } + } + + assert_equal '/home?token=secret', rs.url_for(options) end def test_url_options_override @@ -167,9 +180,9 @@ class UrlOptionsTest < ActionController::TestCase get :from_view, :route => "from_view_url" - assert_equal 'http://www.override.com/from_view?locale=en', @response.body - assert_equal 'http://www.override.com/from_view?locale=en', @controller.send(:from_view_url) - assert_equal 'http://www.override.com/default_url_options/new?locale=en', @controller.url_for(:controller => 'default_url_options') + assert_equal 'http://www.override.com/from_view', @response.body + assert_equal 'http://www.override.com/from_view', @controller.send(:from_view_url) + assert_equal 'http://www.override.com/default_url_options/index', @controller.url_for(:controller => 'default_url_options') end end @@ -179,7 +192,6 @@ class UrlOptionsTest < ActionController::TestCase match "account/overview" end - @controller.class.send(:include, set.url_helpers) assert !@controller.class.action_methods.include?("account_overview_path") end end @@ -191,7 +203,6 @@ class DefaultUrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! end def test_default_url_options_override @@ -242,7 +253,6 @@ class EmptyUrlOptionsTest < ActionController::TestCase def setup super @request.host = 'www.example.com' - rescue_action_in_public! end def test_ensure_url_for_works_as_expected_when_called_with_no_options_if_default_url_options_is_not_set diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index bb4fb7bf07..a42c68a628 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -180,7 +180,7 @@ class PageCachingTest < ActionController::TestCase end [:ok, :no_content, :found, :not_found].each do |status| - [:get, :post, :put, :delete].each do |method| + [:get, :post, :patch, :put, :delete].each do |method| unless method == :get && status == :ok define_method "test_shouldnt_cache_#{method}_with_#{status}_status" do send(method, status) @@ -237,6 +237,7 @@ class ActionCachingTestController < CachingController caches_action :with_format_and_http_param, :cache_path => Proc.new { |c| { :key => 'value' } } caches_action :layout_false, :layout => false caches_action :record_not_found, :four_oh_four, :simple_runtime_error + caches_action :streaming layout 'talk_from_action' @@ -296,6 +297,10 @@ class ActionCachingTestController < CachingController expire_action url_for(:controller => 'action_caching_test', :action => 'index') render :nothing => true end + + def streaming + render :text => "streaming", :stream => true + end end class MockTime < Time @@ -647,6 +652,13 @@ class ActionCacheTest < ActionController::TestCase assert_response 500 end + def test_action_caching_plus_streaming + get :streaming + assert_response :success + assert_match(/streaming/, @response.body) + assert fragment_exist?('hostname.com/action_caching_test/streaming') + end + private def content_to_cache assigns(:cache_this) diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 046396b37c..65c853f6eb 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -937,9 +937,7 @@ class ControllerWithAllTypesOfFilters < PostsController end class ControllerWithTwoLessFilters < ControllerWithAllTypesOfFilters - $vbf = true skip_filter :around_again - $vbf = false skip_filter :after end diff --git a/actionpack/test/controller/force_ssl_test.rb b/actionpack/test/controller/force_ssl_test.rb index 3ea3c06ac4..7feeda25b3 100644 --- a/actionpack/test/controller/force_ssl_test.rb +++ b/actionpack/test/controller/force_ssl_test.rb @@ -50,6 +50,12 @@ class ForceSSLControllerLevelTest < ActionController::TestCase assert_equal "https://test.host/force_ssl_controller_level/banana", redirect_to_url end + def test_banana_redirects_to_https_with_extra_params + get :banana, :token => "secret" + assert_response 301 + assert_equal "https://test.host/force_ssl_controller_level/banana?token=secret", redirect_to_url + end + def test_cheeseburger_redirects_to_https get :cheeseburger assert_response 301 @@ -103,20 +109,6 @@ class ForceSSLExceptActionTest < ActionController::TestCase end end -class ForceSSLExcludeDevelopmentTest < ActionController::TestCase - tests ForceSSLControllerLevel - - def setup - Rails.env.stubs(:development?).returns(false) - end - - def test_development_environment_not_redirects_to_https - Rails.env.stubs(:development?).returns(true) - get :banana - assert_response 200 - end -end - class ForceSSLFlashTest < ActionController::TestCase tests ForceSSLFlash diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index a91e3cafa5..828ea5b0fb 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -274,6 +274,6 @@ class HttpDigestAuthenticationTest < ActionController::TestCase end def decode_credentials(header) - ActionController::HttpAuthentication::Digest.decode_credentials(@response.headers['WWW-Authenticate']) + ActionController::HttpAuthentication::Digest.decode_credentials(header) end end diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index a328372cff..44f033119d 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -63,6 +63,12 @@ class SessionTest < ActiveSupport::TestCase @session.post_via_redirect(path, args, headers) end + def test_patch_via_redirect + path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } + @session.expects(:request_via_redirect).with(:patch, path, args, headers) + @session.patch_via_redirect(path, args, headers) + end + def test_put_via_redirect path = "/somepath"; args = {:id => '1'}; headers = {"X-Test-Header" => "testvalue" } @session.expects(:request_via_redirect).with(:put, path, args, headers) @@ -87,6 +93,12 @@ class SessionTest < ActiveSupport::TestCase @session.post(path,params,headers) end + def test_patch + path = "/index"; params = "blah"; headers = {:location => 'blah'} + @session.expects(:process).with(:patch,path,params,headers) + @session.patch(path,params,headers) + end + def test_put path = "/index"; params = "blah"; headers = {:location => 'blah'} @session.expects(:process).with(:put,path,params,headers) @@ -105,6 +117,12 @@ class SessionTest < ActiveSupport::TestCase @session.head(path,params,headers) end + def test_options + path = "/index"; params = "blah"; headers = {:location => 'blah'} + @session.expects(:process).with(:options,path,params,headers) + @session.options(path,params,headers) + end + def test_xml_http_request_get path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( @@ -125,6 +143,16 @@ class SessionTest < ActiveSupport::TestCase @session.xml_http_request(:post,path,params,headers) end + def test_xml_http_request_patch + path = "/index"; params = "blah"; headers = {:location => 'blah'} + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:patch,path,params,headers_after_xhr) + @session.xml_http_request(:patch,path,params,headers) + end + def test_xml_http_request_put path = "/index"; params = "blah"; headers = {:location => 'blah'} headers_after_xhr = headers.merge( @@ -155,6 +183,16 @@ class SessionTest < ActiveSupport::TestCase @session.xml_http_request(:head,path,params,headers) end + def test_xml_http_request_options + path = "/index"; params = "blah"; headers = {:location => 'blah'} + headers_after_xhr = headers.merge( + "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest", + "HTTP_ACCEPT" => "text/javascript, text/html, application/xml, text/xml, */*" + ) + @session.expects(:process).with(:options,path,params,headers_after_xhr) + @session.xml_http_request(:options,path,params,headers) + end + def test_xml_http_request_override_accept path = "/index"; params = "blah"; headers = {:location => 'blah', "HTTP_ACCEPT" => "application/xml"} headers_after_xhr = headers.merge( @@ -212,7 +250,7 @@ class IntegrationTestUsesCorrectClass < ActionDispatch::IntegrationTest @integration_session.stubs(:generic_url_rewriter) @integration_session.stubs(:process) - %w( get post head put delete ).each do |verb| + %w( get post head patch put delete options ).each do |verb| assert_nothing_raised("'#{verb}' should use integration test methods") { __send__(verb, '/') } end end @@ -535,3 +573,36 @@ class ApplicationIntegrationTest < ActionDispatch::IntegrationTest assert_equal old_env, env end end + +class EnvironmentFilterIntegrationTest < ActionDispatch::IntegrationTest + class TestController < ActionController::Base + def post + render :text => "Created", :status => 201 + end + end + + def self.call(env) + env["action_dispatch.parameter_filter"] = [:password] + routes.call(env) + end + + def self.routes + @routes ||= ActionDispatch::Routing::RouteSet.new + end + + routes.draw do + match '/post', :to => 'environment_filter_integration_test/test#post', :via => :post + end + + def app + self.class + end + + test "filters rack request form vars" do + post "/post", :username => 'cjolly', :password => 'secret' + + assert_equal 'cjolly', request.filtered_parameters['username'] + assert_equal '[FILTERED]', request.filtered_parameters['password'] + assert_equal '[FILTERED]', request.filtered_env['rack.request.form_vars'] + end +end diff --git a/actionpack/test/controller/layout_test.rb b/actionpack/test/controller/layout_test.rb index bc171e201b..c73b36f05e 100644 --- a/actionpack/test/controller/layout_test.rb +++ b/actionpack/test/controller/layout_test.rb @@ -78,6 +78,13 @@ end class DefaultLayoutController < LayoutTest end +class StreamingLayoutController < LayoutTest + def render(*args) + options = args.extract_options! || {} + super(*args, options.merge(:stream => true)) + end +end + class AbsolutePathLayoutController < LayoutTest layout File.expand_path(File.expand_path(__FILE__) + '/../../fixtures/layout_tests/layouts/layout_test') end @@ -122,6 +129,12 @@ class LayoutSetInResponseTest < ActionController::TestCase assert_template :layout => "layouts/layout_test" end + def test_layout_set_when_using_streaming_layout + @controller = StreamingLayoutController.new + get :hello + assert_template :hello + end + def test_layout_set_when_set_in_controller @controller = HasOwnLayoutController.new get :hello diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index 60498822ff..0e4099ddc6 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -132,7 +132,6 @@ class RespondToController < ActionController::Base end end - def iphone_with_html_response_type request.format = :iphone if request.env["HTTP_ACCEPT"] == "text/iphone" @@ -593,6 +592,19 @@ class RenderJsonRespondWithController < RespondWithController format.json { render :json => RenderJsonTestException.new('boom') } end end + + def create + resource = ValidatedCustomer.new(params[:name], 1) + respond_with(resource) do |format| + format.json do + if resource.errors.empty? + render :json => { :valid => true } + else + render :json => { :valid => false } + end + end + end + end end class EmptyRespondWithController < ActionController::Base @@ -757,6 +769,41 @@ class RespondWithControllerTest < ActionController::TestCase end end + def test_using_resource_for_patch_with_html_redirects_on_success + with_test_route_set do + patch :using_resource + assert_equal "text/html", @response.content_type + assert_equal 302, @response.status + assert_equal "http://www.example.com/customers/13", @response.location + assert @response.redirect? + end + end + + def test_using_resource_for_patch_with_html_rerender_on_failure + with_test_route_set do + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + patch :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "Edit world!\n", @response.body + assert_nil @response.location + end + end + + def test_using_resource_for_patch_with_html_rerender_on_failure_even_on_method_override + with_test_route_set do + errors = { :name => :invalid } + Customer.any_instance.stubs(:errors).returns(errors) + @request.env["rack.methodoverride.original_method"] = "POST" + patch :using_resource + assert_equal "text/html", @response.content_type + assert_equal 200, @response.status + assert_equal "Edit world!\n", @response.body + assert_nil @response.location + end + end + def test_using_resource_for_put_with_html_redirects_on_success with_test_route_set do put :using_resource @@ -964,6 +1011,18 @@ class RespondWithControllerTest < ActionController::TestCase assert_match(/"error":"RenderJsonTestException"/, @response.body) end + def test_api_response_with_valid_resource_respect_override_block + @controller = RenderJsonRespondWithController.new + post :create, :name => "sikachu", :format => :json + assert_equal '{"valid":true}', @response.body + end + + def test_api_response_with_invalid_resource_respect_override_block + @controller = RenderJsonRespondWithController.new + post :create, :name => "david", :format => :json + assert_equal '{"valid":false}', @response.body + end + def test_no_double_render_is_raised @request.accept = "text/html" assert_raise ActionView::MissingTemplate do @@ -1075,7 +1134,7 @@ class PostController < AbstractPostController around_filter :with_iphone def index - respond_to(:html, :iphone) + respond_to(:html, :iphone, :js) end protected @@ -1122,4 +1181,45 @@ class MimeControllerLayoutsTest < ActionController::TestCase get :index assert_equal '<html><div id="super_iphone">Super iPhone</div></html>', @response.body end + + def test_non_navigational_format_with_no_template_fallbacks_to_html_template_with_no_layout + get :index, :format => :js + assert_equal "Hello Firefox", @response.body + end +end + +class FlashResponder < ActionController::Responder + def initialize(controller, resources, options={}) + super + end + + def to_html + controller.flash[:notice] = 'Success' + super + end +end + +class FlashResponderController < ActionController::Base + self.responder = FlashResponder + respond_to :html + + def index + respond_with Object.new do |format| + format.html { render :text => 'HTML' } + end + end +end + +class FlashResponderControllerTest < ActionController::TestCase + tests FlashResponderController + + def test_respond_with_block_executed + get :index + assert_equal 'HTML', @response.body + end + + def test_flash_responder_executed + get :index + assert_equal 'Success', flash[:notice] + end end diff --git a/actionpack/test/controller/redirect_test.rb b/actionpack/test/controller/redirect_test.rb index b1d76150f8..6dab42d75d 100644 --- a/actionpack/test/controller/redirect_test.rb +++ b/actionpack/test/controller/redirect_test.rb @@ -103,6 +103,14 @@ class RedirectController < ActionController::Base redirect_to proc { {:action => "hello_world"} } end + def redirect_with_header_break + redirect_to "/lol\r\nwat" + end + + def redirect_with_null_bytes + redirect_to "\000/lol\r\nwat" + end + def rescue_errors(e) raise e end protected @@ -120,6 +128,18 @@ class RedirectTest < ActionController::TestCase assert_equal "http://test.host/redirect/hello_world", redirect_to_url end + def test_redirect_with_header_break + get :redirect_with_header_break + assert_response :redirect + assert_equal "http://test.host/lolwat", redirect_to_url + end + + def test_redirect_with_null_bytes + get :redirect_with_null_bytes + assert_response :redirect + assert_equal "http://test.host/lolwat", redirect_to_url + end + def test_redirect_with_no_status get :simple_redirect assert_response 302 diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index fef9fbb175..fce13d096c 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -91,6 +91,16 @@ class TestController < ActionController::Base render :action => 'hello_world' end + def conditional_hello_with_expires_in_with_must_revalidate + expires_in 1.minute, :must_revalidate => true + render :action => 'hello_world' + end + + def conditional_hello_with_expires_in_with_public_and_must_revalidate + expires_in 1.minute, :public => true, :must_revalidate => true + render :action => 'hello_world' + end + def conditional_hello_with_expires_in_with_public_with_more_keys expires_in 1.minute, :public => true, 'max-stale' => 5.hours render :action => 'hello_world' @@ -543,12 +553,33 @@ class TestController < ActionController::Base render :partial => 'partial' end + def partial_html_erb + render :partial => 'partial_html_erb' + end + def render_to_string_with_partial @partial_only = render_to_string :partial => "partial_only" @partial_with_locals = render_to_string :partial => "customer", :locals => { :customer => Customer.new("david") } render :template => "test/hello_world" end + def render_to_string_with_template_and_html_partial + @text = render_to_string :template => "test/with_partial", :formats => [:text] + @html = render_to_string :template => "test/with_partial", :formats => [:html] + render :template => "test/with_html_partial" + end + + def render_to_string_and_render_with_different_formats + @html = render_to_string :template => "test/with_partial", :formats => [:html] + render :template => "test/with_partial", :formats => [:text] + end + + def render_template_within_a_template_with_other_format + render :template => "test/with_xml_template", + :formats => [:html], + :layout => "with_html_partial" + end + def partial_with_counter render :partial => "counter", :locals => { :counter_counter => 5 } end @@ -991,6 +1022,7 @@ class RenderTest < ActionController::TestCase def test_accessing_local_assigns_in_inline_template get :accessing_local_assigns_in_inline_template, :local_name => "Local David" assert_equal "Goodbye, Local David", @response.body + assert_equal "text/html", @response.content_type end def test_should_implicitly_render_html_template_from_xhr_request @@ -1236,22 +1268,57 @@ class RenderTest < ActionController::TestCase def test_partial_only get :partial_only assert_equal "only partial", @response.body + assert_equal "text/html", @response.content_type end def test_should_render_html_formatted_partial get :partial - assert_equal 'partial html', @response.body + assert_equal "partial html", @response.body + assert_equal "text/html", @response.content_type + end + + def test_render_html_formatted_partial_even_with_other_mime_time_in_accept + @request.accept = "text/javascript, text/html" + + get :partial_html_erb + + assert_equal "partial.html.erb", @response.body.strip + assert_equal "text/html", @response.content_type end def test_should_render_html_partial_with_formats get :partial_formats_html - assert_equal 'partial html', @response.body + assert_equal "partial html", @response.body + assert_equal "text/html", @response.content_type end def test_render_to_string_partial get :render_to_string_with_partial assert_equal "only partial", assigns(:partial_only) assert_equal "Hello: david", assigns(:partial_with_locals) + assert_equal "text/html", @response.content_type + end + + def test_render_to_string_with_template_and_html_partial + get :render_to_string_with_template_and_html_partial + assert_equal "**only partial**\n", assigns(:text) + assert_equal "<strong>only partial</strong>\n", assigns(:html) + assert_equal "<strong>only html partial</strong>\n", @response.body + assert_equal "text/html", @response.content_type + end + + def test_render_to_string_and_render_with_different_formats + get :render_to_string_and_render_with_different_formats + assert_equal "<strong>only partial</strong>\n", assigns(:html) + assert_equal "**only partial**\n", @response.body + assert_equal "text/plain", @response.content_type + end + + def test_render_template_within_a_template_with_other_format + get :render_template_within_a_template_with_other_format + expected = "only html partial<p>This is grand!</p>" + assert_equal expected, @response.body.strip + assert_equal "text/html", @response.content_type end def test_partial_with_counter @@ -1399,6 +1466,16 @@ class ExpiresInRenderTest < ActionController::TestCase assert_equal "max-age=60, public", @response.headers["Cache-Control"] end + def test_expires_in_header_with_must_revalidate + get :conditional_hello_with_expires_in_with_must_revalidate + assert_equal "max-age=60, private, must-revalidate", @response.headers["Cache-Control"] + end + + def test_expires_in_header_with_public_and_must_revalidate + get :conditional_hello_with_expires_in_with_public_and_must_revalidate + assert_equal "max-age=60, public, must-revalidate", @response.headers["Cache-Control"] + end + def test_expires_in_header_with_additional_headers get :conditional_hello_with_expires_in_with_public_with_more_keys assert_equal "max-age=60, public, max-stale=18000", @response.headers["Cache-Control"] @@ -1413,6 +1490,13 @@ class ExpiresInRenderTest < ActionController::TestCase get :conditional_hello_with_expires_now assert_equal "no-cache", @response.headers["Cache-Control"] end + + def test_date_header_when_expires_in + time = Time.mktime(2011,10,30) + Time.stubs(:now).returns(time) + get :conditional_hello_with_expires_in + assert_equal Time.now.httpdate, @response.headers["Date"] + end end class LastModifiedRenderTest < ActionController::TestCase diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb index e6d3fa74f2..066cd523be 100644 --- a/actionpack/test/controller/request_forgery_protection_test.rb +++ b/actionpack/test/controller/request_forgery_protection_test.rb @@ -35,6 +35,24 @@ module RequestForgeryProtectionActions def form_for_without_protection render :inline => "<%= form_for(:some_resource, :authenticity_token => false ) {} %>" end + + def form_for_remote + render :inline => "<%= form_for(:some_resource, :remote => true ) {} %>" + end + + def form_for_remote_with_token + render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => true ) {} %>" + end + + def form_for_with_token + render :inline => "<%= form_for(:some_resource, :authenticity_token => true ) {} %>" + end + + def form_for_remote_with_external_token + render :inline => "<%= form_for(:some_resource, :remote => true, :authenticity_token => 'external_token') {} %>" + end + + def rescue_action(e) raise e end end # sample controllers @@ -43,7 +61,7 @@ class RequestForgeryProtectionController < ActionController::Base protect_from_forgery :only => %w(index meta) end -class RequestForgeryProtectionControllerUsingOldBehaviour < ActionController::Base +class RequestForgeryProtectionControllerUsingException < ActionController::Base include RequestForgeryProtectionActions protect_from_forgery :only => %w(index meta) @@ -98,6 +116,60 @@ module RequestForgeryProtectionTests assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token end + def test_should_render_form_without_token_tag_if_remote + assert_not_blocked do + get :form_for_remote + end + assert_no_match(/authenticity_token/, response.body) + end + + def test_should_render_form_with_token_tag_if_remote_and_embedding_token_is_on + original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms + begin + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true + assert_not_blocked do + get :form_for_remote + end + assert_match(/authenticity_token/, response.body) + ensure + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original + end + end + + def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested_and_embedding_is_on + original = ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms + begin + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = true + assert_not_blocked do + get :form_for_remote_with_external_token + end + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + ensure + ActionView::Helpers::FormTagHelper.embed_authenticity_token_in_remote_forms = original + end + end + + def test_should_render_form_with_token_tag_if_remote_and_external_authenticity_token_requested + assert_not_blocked do + get :form_for_remote_with_external_token + end + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', 'external_token' + end + + def test_should_render_form_with_token_tag_if_remote_and_authenticity_token_requested + assert_not_blocked do + get :form_for_remote_with_token + end + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + end + + def test_should_render_form_with_token_tag_with_authenticity_token_requested + assert_not_blocked do + get :form_for_with_token + end + assert_select 'form>div>input[name=?][value=?]', 'custom_authenticity_token', @token + end + def test_should_allow_get assert_not_blocked { get :index } end @@ -114,6 +186,10 @@ module RequestForgeryProtectionTests assert_blocked { post :index, :format=>'xml' } end + def test_should_not_allow_patch_without_token + assert_blocked { patch :index } + end + def test_should_not_allow_put_without_token assert_blocked { put :index } end @@ -130,6 +206,10 @@ module RequestForgeryProtectionTests assert_not_blocked { post :index, :custom_authenticity_token => @token } end + def test_should_allow_patch_with_token + assert_not_blocked { patch :index, :custom_authenticity_token => @token } + end + def test_should_allow_put_with_token assert_not_blocked { put :index, :custom_authenticity_token => @token } end @@ -148,6 +228,11 @@ module RequestForgeryProtectionTests assert_not_blocked { delete :index } end + def test_should_allow_patch_with_token_in_header + @request.env['HTTP_X_CSRF_TOKEN'] = @token + assert_not_blocked { patch :index } + end + def test_should_allow_put_with_token_in_header @request.env['HTTP_X_CSRF_TOKEN'] = @token assert_not_blocked { put :index } @@ -202,7 +287,7 @@ class RequestForgeryProtectionControllerTest < ActionController::TestCase end end -class RequestForgeryProtectionControllerUsingOldBehaviourTest < ActionController::TestCase +class RequestForgeryProtectionControllerUsingExceptionTest < ActionController::TestCase include RequestForgeryProtectionTests def assert_blocked assert_raises(ActionController::InvalidAuthenticityToken) do @@ -232,7 +317,7 @@ class FreeCookieControllerTest < ActionController::TestCase end def test_should_allow_all_methods_without_token - [:post, :put, :delete].each do |method| + [:post, :patch, :put, :delete].each do |method| assert_nothing_raised { send(method, :index)} end end diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb index 73d72fe4d6..3c0a5d36ca 100644 --- a/actionpack/test/controller/resources_test.rb +++ b/actionpack/test/controller/resources_test.rb @@ -158,7 +158,7 @@ class ResourcesTest < ActionController::TestCase end def test_with_collection_actions - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } + actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } with_routing do |set| set.draw do @@ -167,6 +167,7 @@ class ResourcesTest < ActionController::TestCase put :b, :on => :collection post :c, :on => :collection delete :d, :on => :collection + patch :e, :on => :collection end end @@ -185,7 +186,7 @@ class ResourcesTest < ActionController::TestCase end def test_with_collection_actions_and_name_prefix - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } + actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } with_routing do |set| set.draw do @@ -195,6 +196,7 @@ class ResourcesTest < ActionController::TestCase put :b, :on => :collection post :c, :on => :collection delete :d, :on => :collection + patch :e, :on => :collection end end end @@ -241,7 +243,7 @@ class ResourcesTest < ActionController::TestCase end def test_with_collection_action_and_name_prefix_and_formatted - actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete } + actions = { 'a' => :get, 'b' => :put, 'c' => :post, 'd' => :delete, 'e' => :patch } with_routing do |set| set.draw do @@ -251,6 +253,7 @@ class ResourcesTest < ActionController::TestCase put :b, :on => :collection post :c, :on => :collection delete :d, :on => :collection + patch :e, :on => :collection end end end @@ -270,7 +273,7 @@ class ResourcesTest < ActionController::TestCase end def test_with_member_action - [:put, :post].each do |method| + [:patch, :put, :post].each do |method| with_restful_routing :messages, :member => { :mark => method } do mark_options = {:action => 'mark', :id => '1'} mark_path = "/messages/1/mark" @@ -294,7 +297,7 @@ class ResourcesTest < ActionController::TestCase end def test_member_when_override_paths_for_default_restful_actions_with - [:put, :post].each do |method| + [:patch, :put, :post].each do |method| with_restful_routing :messages, :member => { :mark => method }, :path_names => {:new => 'nuevo'} do mark_options = {:action => 'mark', :id => '1', :controller => "messages"} mark_path = "/messages/1/mark" @@ -311,7 +314,7 @@ class ResourcesTest < ActionController::TestCase end def test_with_two_member_actions_with_same_method - [:put, :post].each do |method| + [:patch, :put, :post].each do |method| with_routing do |set| set.draw do resources :messages do @@ -564,7 +567,7 @@ class ResourcesTest < ActionController::TestCase end def test_singleton_resource_with_member_action - [:put, :post].each do |method| + [:patch, :put, :post].each do |method| with_routing do |set| set.draw do resource :account do @@ -586,7 +589,7 @@ class ResourcesTest < ActionController::TestCase end def test_singleton_resource_with_two_member_actions_with_same_method - [:put, :post].each do |method| + [:patch, :put, :post].each do |method| with_routing do |set| set.draw do resource :account do @@ -651,13 +654,17 @@ class ResourcesTest < ActionController::TestCase end end - def test_should_not_allow_delete_or_put_on_collection_path + def test_should_not_allow_delete_or_patch_or_put_on_collection_path controller_name = :messages with_restful_routing controller_name do options = { :controller => controller_name.to_s } collection_path = "/#{controller_name}" assert_raise(ActionController::RoutingError) do + assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :patch) + end + + assert_raise(ActionController::RoutingError) do assert_recognizes(options.merge(:action => 'update'), :path => collection_path, :method => :put) end diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 44a40e0665..272a7da8c5 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -59,11 +59,11 @@ end class MockController def self.build(helpers) Class.new do - def url_for(options) + def url_options + options = super options[:protocol] ||= "http" options[:host] ||= "test.host" - - super(options) + options end include helpers @@ -145,6 +145,57 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal 'Not Found', get(URI('http://example.org/journey/omg-faithfully')) end + def test_star_paths_are_greedy + rs.draw do + match "/*path", :to => lambda { |env| + x = env["action_dispatch.request.path_parameters"][:path] + [200, {}, [x]] + }, :format => false + end + + u = URI('http://example.org/foo/bar.html') + assert_equal u.path.sub(/^\//, ''), get(u) + end + + def test_star_paths_are_greedy_but_not_too_much + rs.draw do + match "/*path", :to => lambda { |env| + x = JSON.dump env["action_dispatch.request.path_parameters"] + [200, {}, [x]] + } + end + + expected = { "path" => "foo/bar", "format" => "html" } + u = URI('http://example.org/foo/bar.html') + assert_equal expected, JSON.parse(get(u)) + end + + def test_optional_star_paths_are_greedy + rs.draw do + match "/(*filters)", :to => lambda { |env| + x = env["action_dispatch.request.path_parameters"][:filters] + [200, {}, [x]] + }, :format => false + end + + u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794') + assert_equal u.path.sub(/^\//, ''), get(u) + end + + def test_optional_star_paths_are_greedy_but_not_too_much + rs.draw do + match "/(*filters)", :to => lambda { |env| + x = JSON.dump env["action_dispatch.request.path_parameters"] + [200, {}, [x]] + } + end + + expected = { "filters" => "ne_27.065938,-80.6092/sw_25.489856,-82", + "format" => "542794" } + u = URI('http://example.org/ne_27.065938,-80.6092/sw_25.489856,-82.542794') + assert_equal expected, JSON.parse(get(u)) + end + def test_regexp_precidence @rs.draw do match '/whois/:domain', :constraints => { @@ -242,36 +293,6 @@ class LegacyRouteSetTests < ActiveSupport::TestCase test_default_setup end - def test_time_recognition - # We create many routes to make situation more realistic - @rs = ::ActionDispatch::Routing::RouteSet.new - @rs.draw { - root :to => "search#new", :as => "frontpage" - resources :videos do - resources :comments - resource :file, :controller => 'video_file' - resource :share, :controller => 'video_shares' - resource :abuse, :controller => 'video_abuses' - end - resources :abuses, :controller => 'video_abuses' - resources :video_uploads - resources :video_visits - - resources :users do - resource :settings - resources :videos - end - resources :channels do - resources :videos, :controller => 'channel_videos' - end - resource :session - resource :lost_password - match 'search' => 'search#index', :as => 'search' - resources :pages - match ':controller/:action/:id' - } - end - def test_route_with_colon_first rs.draw do match '/:controller/:action/:id', :action => 'index', :id => nil @@ -416,6 +437,15 @@ class LegacyRouteSetTests < ActiveSupport::TestCase assert_equal("/", routes.send(:root_path)) end + def test_named_route_root_without_hash + rs.draw do + root "hello#index" + end + routes = setup_for_named_route + assert_equal("http://test.host/", routes.send(:root_url)) + assert_equal("/", routes.send(:root_path)) + end + def test_named_route_with_regexps rs.draw do match 'page/:year/:month/:day/:title' => 'page#show', :as => 'article', @@ -652,11 +682,12 @@ class LegacyRouteSetTests < ActiveSupport::TestCase match '/match' => 'books#get', :via => :get match '/match' => 'books#post', :via => :post match '/match' => 'books#put', :via => :put + match '/match' => 'books#patch', :via => :patch match '/match' => 'books#delete', :via => :delete end end - %w(GET POST PUT DELETE).each do |request_method| + %w(GET PATCH POST PUT DELETE).each do |request_method| define_method("test_request_method_recognized_with_#{request_method}") do setup_request_method_routes_for(request_method) params = rs.recognize_path("/match", :method => request_method) @@ -1039,6 +1070,7 @@ class RouteSetTest < ActiveSupport::TestCase post "/people" => "people#create" get "/people/:id" => "people#show", :as => "person" put "/people/:id" => "people#update" + patch "/people/:id" => "people#update" delete "/people/:id" => "people#destroy" end @@ -1051,6 +1083,9 @@ class RouteSetTest < ActiveSupport::TestCase params = set.recognize_path("/people/5", :method => :put) assert_equal("update", params[:action]) + params = set.recognize_path("/people/5", :method => :patch) + assert_equal("update", params[:action]) + assert_raise(ActionController::UnknownHttpMethod) { set.recognize_path("/people", :method => :bacon) } @@ -1063,6 +1098,10 @@ class RouteSetTest < ActiveSupport::TestCase assert_equal("update", params[:action]) assert_equal("5", params[:id]) + params = set.recognize_path("/people/5", :method => :patch) + assert_equal("update", params[:action]) + assert_equal("5", params[:id]) + params = set.recognize_path("/people/5", :method => :delete) assert_equal("destroy", params[:action]) assert_equal("5", params[:id]) @@ -1116,6 +1155,7 @@ class RouteSetTest < ActiveSupport::TestCase set.draw do get "people/:id" => "people#show", :as => "person" put "people/:id" => "people#update" + patch "people/:id" => "people#update" get "people/:id(.:format)" => "people#show" end @@ -1126,6 +1166,9 @@ class RouteSetTest < ActiveSupport::TestCase params = set.recognize_path("/people/5", :method => :put) assert_equal("update", params[:action]) + params = set.recognize_path("/people/5", :method => :patch) + assert_equal("update", params[:action]) + params = set.recognize_path("/people/5.png", :method => :get) assert_equal("show", params[:action]) assert_equal("5", params[:id]) diff --git a/actionpack/test/controller/send_file_test.rb b/actionpack/test/controller/send_file_test.rb index 36884846be..3af17f495c 100644 --- a/actionpack/test/controller/send_file_test.rb +++ b/actionpack/test/controller/send_file_test.rb @@ -23,10 +23,6 @@ class SendFileController < ActionController::Base def data send_data(file_data, options) end - - def multibyte_text_data - send_data("Кирилица\n祝您好運.", options) - end end class SendFileTest < ActionController::TestCase diff --git a/actionpack/test/controller/sweeper_test.rb b/actionpack/test/controller/sweeper_test.rb new file mode 100644 index 0000000000..0561efc62f --- /dev/null +++ b/actionpack/test/controller/sweeper_test.rb @@ -0,0 +1,16 @@ +require 'abstract_unit' + + +class SweeperTest < ActionController::TestCase + + class ::AppSweeper < ActionController::Caching::Sweeper; end + + def test_sweeper_should_not_ignore_unknown_method_calls + sweeper = ActionController::Caching::Sweeper.send(:new) + assert_raise NameError do + sweeper.instance_eval do + some_method_that_doesnt_exist + end + end + end +end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index caf76c7e61..ecba9fed22 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -1,6 +1,5 @@ require 'abstract_unit' require 'controller/fake_controllers' -require 'active_support/ordered_hash' class TestCaseTest < ActionController::TestCase class TestController < ActionController::Base @@ -120,6 +119,7 @@ XML def test_assigns @foo = "foo" + @foo_hash = {:foo => :bar} render :nothing => true end @@ -155,14 +155,14 @@ XML end def test_raw_post_handling - params = ActiveSupport::OrderedHash[:page, {:name => 'page name'}, 'some key', 123] + params = Hash[:page, {:name => 'page name'}, 'some key', 123] post :render_raw_post, params.dup assert_equal params.to_query, @response.body end def test_body_stream - params = ActiveSupport::OrderedHash[:page, { :name => 'page name' }, 'some key', 123] + params = Hash[:page, { :name => 'page name' }, 'some key', 123] post :render_body, params.dup @@ -293,6 +293,10 @@ XML assert_equal "foo", assigns("foo") assert_equal "foo", assigns[:foo] assert_equal "foo", assigns["foo"] + + # but the assigned variable should not have its own keys stringified + expected_hash = { :foo => :bar } + assert_equal expected_hash, assigns(:foo_hash) end def test_view_assigns diff --git a/actionpack/test/controller/url_for_test.rb b/actionpack/test/controller/url_for_test.rb index 288efbf7c3..aa233d6135 100644 --- a/actionpack/test/controller/url_for_test.rb +++ b/actionpack/test/controller/url_for_test.rb @@ -16,6 +16,10 @@ module AbstractController W.default_url_options[:host] = 'www.basecamphq.com' end + def add_port! + W.default_url_options[:port] = 3000 + end + def add_numeric_host! W.default_url_options[:host] = '127.0.0.1' end @@ -121,6 +125,14 @@ module AbstractController ) end + def test_default_port + add_host! + add_port! + assert_equal('http://www.basecamphq.com:3000/c/a/i', + W.new.url_for(:controller => 'c', :action => 'a', :id => 'i') + ) + end + def test_protocol add_host! assert_equal('https://www.basecamphq.com/c/a/i', diff --git a/actionpack/test/dispatch/callbacks_test.rb b/actionpack/test/dispatch/callbacks_test.rb index eed2eca2ab..f767b07e75 100644 --- a/actionpack/test/dispatch/callbacks_test.rb +++ b/actionpack/test/dispatch/callbacks_test.rb @@ -27,6 +27,12 @@ class DispatcherTest < ActiveSupport::TestCase dispatch assert_equal 4, Foo.a assert_equal 4, Foo.b + + dispatch do |env| + raise "error" + end rescue nil + assert_equal 6, Foo.a + assert_equal 6, Foo.b end def test_to_prepare_and_cleanup_delegation @@ -44,8 +50,9 @@ class DispatcherTest < ActiveSupport::TestCase private def dispatch(&block) - @dispatcher ||= ActionDispatch::Callbacks.new(block || DummyApp.new) - @dispatcher.call({'rack.input' => StringIO.new('')}) + ActionDispatch::Callbacks.new(block || DummyApp.new).call( + {'rack.input' => StringIO.new('')} + ) end end diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb index 831f3db3e2..4191ed1ff4 100644 --- a/actionpack/test/dispatch/middleware_stack_test.rb +++ b/actionpack/test/dispatch/middleware_stack_test.rb @@ -81,6 +81,12 @@ class MiddlewareStackTest < ActiveSupport::TestCase assert_equal BazMiddleware, @stack[0].klass end + test "swaps one middleware out for same middleware class" do + assert_equal FooMiddleware, @stack[0].klass + @stack.swap(FooMiddleware, FooMiddleware, Proc.new { |env| [500, {}, ['error!']] }) + assert_equal FooMiddleware, @stack[0].klass + end + test "raise an error on invalid index" do assert_raise RuntimeError do @stack.insert("HiyaMiddleware", BazMiddleware) diff --git a/actionpack/test/dispatch/mime_type_test.rb b/actionpack/test/dispatch/mime_type_test.rb index db21080c42..e3f9faaa64 100644 --- a/actionpack/test/dispatch/mime_type_test.rb +++ b/actionpack/test/dispatch/mime_type_test.rb @@ -69,6 +69,18 @@ class MimeTypeTest < ActiveSupport::TestCase assert_equal expect, Mime::Type.parse(accept) end + test "parse single media range with q" do + accept = "text/html;q=0.9" + expect = [Mime::HTML] + assert_equal expect, Mime::Type.parse(accept) + end + + test "parse arbitarry media type parameters" do + accept = 'multipart/form-data; boundary="simple boundary"' + expect = [Mime::MULTIPART_FORM] + assert_equal expect, Mime::Type.parse(accept) + end + # Accept header send with user HTTP_USER_AGENT: Sunrise/0.42j (Windows XP) test "parse broken acceptlines" do accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/*,,*/*;q=0.5" diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index 5b3d38c48c..6c8b22c47f 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -42,7 +42,7 @@ 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' => 'unknown,3.4.5.6' + 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' @@ -63,7 +63,7 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,192.168.0.1' assert_equal 'unknown', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 172.31.4.4' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 172.31.4.4' assert_equal '3.4.5.6', request.remote_ip request = stub_request 'HTTP_X_FORWARDED_FOR' => '1.1.1.1', @@ -85,7 +85,7 @@ class RequestTest < ActiveSupport::TestCase :ip_spoofing_check => false assert_equal '2.2.2.2', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '8.8.8.8, 9.9.9.9' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 8.8.8.8' assert_equal '9.9.9.9', request.remote_ip end @@ -94,8 +94,8 @@ class RequestTest < ActiveSupport::TestCase assert_equal '127.0.0.1', request.remote_ip end - test "remote ip with user specified trusted proxies" do - @trusted_proxies = /^67\.205\.106\.73$/i + test "remote ip with user specified trusted proxies String" do + @trusted_proxies = "67.205.106.73" request = stub_request 'REMOTE_ADDR' => '67.205.106.73', 'HTTP_X_FORWARDED_FOR' => '3.4.5.6' @@ -116,8 +116,19 @@ class RequestTest < ActiveSupport::TestCase request = stub_request 'HTTP_X_FORWARDED_FOR' => 'unknown,67.205.106.73' assert_equal 'unknown', request.remote_ip - request = stub_request 'HTTP_X_FORWARDED_FOR' => '9.9.9.9, 3.4.5.6, 10.0.0.1, 67.205.106.73' + request = stub_request 'HTTP_X_FORWARDED_FOR' => '3.4.5.6, 9.9.9.9, 10.0.0.1, 67.205.106.73' + assert_equal '3.4.5.6', request.remote_ip + end + + test "remote ip with user specified trusted proxies Regexp" do + @trusted_proxies = /^67\.205\.106\.73$/i + + request = stub_request 'REMOTE_ADDR' => '67.205.106.73', + '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 '10.0.0.1', request.remote_ip end test "domains" do @@ -314,14 +325,14 @@ class RequestTest < ActiveSupport::TestCase end test "String request methods" do - [:get, :post, :put, :delete].each do |method| + [:get, :post, :patch, :put, :delete].each do |method| request = stub_request 'REQUEST_METHOD' => method.to_s.upcase assert_equal method.to_s.upcase, request.method end end test "Symbol forms of request methods via method_symbol" do - [:get, :post, :put, :delete].each do |method| + [:get, :post, :patch, :put, :delete].each do |method| request = stub_request 'REQUEST_METHOD' => method.to_s.upcase assert_equal method, request.method_symbol end @@ -335,7 +346,7 @@ class RequestTest < ActiveSupport::TestCase end test "allow method hacking on post" do - %w(GET OPTIONS PUT POST DELETE).each do |method| + %w(GET OPTIONS PATCH PUT POST DELETE).each do |method| request = stub_request "REQUEST_METHOD" => method.to_s.upcase assert_equal(method == "HEAD" ? "GET" : method, request.method) end @@ -349,7 +360,7 @@ class RequestTest < ActiveSupport::TestCase end test "restrict method hacking" do - [:get, :put, :delete].each do |method| + [:get, :patch, :put, :delete].each do |method| request = stub_request 'REQUEST_METHOD' => method.to_s.upcase, 'action_dispatch.request.request_parameters' => { :_method => 'put' } assert_equal method.to_s.upcase, request.method @@ -364,6 +375,13 @@ class RequestTest < ActiveSupport::TestCase assert request.head? end + test "post masquerading as patch" do + request = stub_request 'REQUEST_METHOD' => 'PATCH', "rack.methodoverride.original_method" => "POST" + assert_equal "POST", request.method + assert_equal "PATCH", request.request_method + assert request.patch? + end + test "post masquerading as put" do request = stub_request 'REQUEST_METHOD' => 'PUT', "rack.methodoverride.original_method" => "POST" assert_equal "POST", request.method diff --git a/actionpack/test/dispatch/routing_assertions_test.rb b/actionpack/test/dispatch/routing_assertions_test.rb index 9f95d82129..e953029456 100644 --- a/actionpack/test/dispatch/routing_assertions_test.rb +++ b/actionpack/test/dispatch/routing_assertions_test.rb @@ -3,6 +3,7 @@ require 'controller/fake_controllers' class SecureArticlesController < ArticlesController; end class BlockArticlesController < ArticlesController; end +class QueryArticlesController < ArticlesController; end class RoutingAssertionsTest < ActionController::TestCase @@ -18,6 +19,10 @@ class RoutingAssertionsTest < ActionController::TestCase scope 'block', :constraints => lambda { |r| r.ssl? } do resources :articles, :controller => 'block_articles' end + + scope 'query', :constraints => lambda { |r| r.params[:use_query] == 'true' } do + resources :articles, :controller => 'query_articles' + end end end @@ -62,6 +67,13 @@ class RoutingAssertionsTest < ActionController::TestCase assert_recognizes({ :controller => 'block_articles', :action => 'index' }, 'https://test.host/block/articles') end + def test_assert_recognizes_with_query_constraint + assert_raise(ActionController::RoutingError) do + assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'false' }, '/query/articles', { :use_query => 'false' }) + end + assert_recognizes({ :controller => 'query_articles', :action => 'index', :use_query => 'true' }, '/query/articles', { :use_query => 'true' }) + end + def test_assert_routing assert_routing('/articles', :controller => 'articles', :action => 'index') end diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 93f5419487..cc4279d9dd 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -157,8 +157,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end resources :posts do - get :archive, :on => :collection - get :toggle_view, :on => :collection + get :archive, :toggle_view, :on => :collection post :preview, :on => :member resource :subscription @@ -476,6 +475,11 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest get :preview, :on => :member end + resources :profiles, :param => :username do + get :details, :on => :member + resources :messages + end + scope :as => "routes" do get "/c/:id", :as => :collision, :to => "collision#show" get "/collision", :to => "collision#show" @@ -580,52 +584,42 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest include Routes.url_helpers def test_logout - with_test_routes do - delete '/logout' - assert_equal 'sessions#destroy', @response.body + delete '/logout' + assert_equal 'sessions#destroy', @response.body - assert_equal '/logout', logout_path - assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true) - end + assert_equal '/logout', logout_path + assert_equal '/logout', url_for(:controller => 'sessions', :action => 'destroy', :only_path => true) end def test_login - with_test_routes do - get '/login' - assert_equal 'sessions#new', @response.body - assert_equal '/login', login_path + get '/login' + assert_equal 'sessions#new', @response.body + assert_equal '/login', login_path - post '/login' - assert_equal 'sessions#create', @response.body + post '/login' + assert_equal 'sessions#create', @response.body - assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) - assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) + assert_equal '/login', url_for(:controller => 'sessions', :action => 'create', :only_path => true) + assert_equal '/login', url_for(:controller => 'sessions', :action => 'new', :only_path => true) - assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create') - assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url - end + assert_equal 'http://rubyonrails.org/login', Routes.url_for(:controller => 'sessions', :action => 'create') + assert_equal 'http://rubyonrails.org/login', Routes.url_helpers.login_url end def test_login_redirect - with_test_routes do - get '/account/login' - verify_redirect 'http://www.example.com/login' - end + get '/account/login' + verify_redirect 'http://www.example.com/login' end def test_logout_redirect_without_to - with_test_routes do - assert_equal '/account/logout', logout_redirect_path - get '/account/logout' - verify_redirect 'http://www.example.com/logout' - end + assert_equal '/account/logout', logout_redirect_path + get '/account/logout' + verify_redirect 'http://www.example.com/logout' end def test_namespace_redirect - with_test_routes do - get '/private' - verify_redirect 'http://www.example.com/private/index' - end + get '/private' + verify_redirect 'http://www.example.com/private/index' end def test_namespace_with_controller_segment @@ -641,189 +635,159 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end def test_session_singleton_resource - with_test_routes do - get '/session' - assert_equal 'sessions#create', @response.body - assert_equal '/session', session_path + get '/session' + assert_equal 'sessions#create', @response.body + assert_equal '/session', session_path - post '/session' - assert_equal 'sessions#create', @response.body + post '/session' + assert_equal 'sessions#create', @response.body - put '/session' - assert_equal 'sessions#update', @response.body + put '/session' + assert_equal 'sessions#update', @response.body - delete '/session' - assert_equal 'sessions#destroy', @response.body + delete '/session' + assert_equal 'sessions#destroy', @response.body - get '/session/new' - assert_equal 'sessions#new', @response.body - assert_equal '/session/new', new_session_path + get '/session/new' + assert_equal 'sessions#new', @response.body + assert_equal '/session/new', new_session_path - get '/session/edit' - assert_equal 'sessions#edit', @response.body - assert_equal '/session/edit', edit_session_path + get '/session/edit' + assert_equal 'sessions#edit', @response.body + assert_equal '/session/edit', edit_session_path - post '/session/reset' - assert_equal 'sessions#reset', @response.body - assert_equal '/session/reset', reset_session_path - end + post '/session/reset' + assert_equal 'sessions#reset', @response.body + assert_equal '/session/reset', reset_session_path end def test_session_info_nested_singleton_resource - with_test_routes do - get '/session/info' - assert_equal 'infos#show', @response.body - assert_equal '/session/info', session_info_path - end + get '/session/info' + assert_equal 'infos#show', @response.body + assert_equal '/session/info', session_info_path end def test_member_on_resource - with_test_routes do - get '/session/crush' - assert_equal 'sessions#crush', @response.body - assert_equal '/session/crush', crush_session_path - end + get '/session/crush' + assert_equal 'sessions#crush', @response.body + assert_equal '/session/crush', crush_session_path end def test_redirect_modulo - with_test_routes do - get '/account/modulo/name' - verify_redirect 'http://www.example.com/names' - end + get '/account/modulo/name' + verify_redirect 'http://www.example.com/names' end def test_redirect_proc - with_test_routes do - get '/account/proc/person' - verify_redirect 'http://www.example.com/people' - end + get '/account/proc/person' + verify_redirect 'http://www.example.com/people' end def test_redirect_proc_with_request - with_test_routes do - get '/account/proc_req' - verify_redirect 'http://www.example.com/GET' - end + get '/account/proc_req' + verify_redirect 'http://www.example.com/GET' end def test_redirect_hash_with_subdomain - with_test_routes do - get '/mobile' - verify_redirect 'http://mobile.example.com/mobile' - end + get '/mobile' + verify_redirect 'http://mobile.example.com/mobile' end def test_redirect_hash_with_host - with_test_routes do - get '/super_new_documentation?section=top' - verify_redirect 'http://super-docs.com/super_new_documentation?section=top' - end + get '/super_new_documentation?section=top' + verify_redirect 'http://super-docs.com/super_new_documentation?section=top' end def test_redirect_class - with_test_routes do - get '/youtube_favorites/oHg5SJYRHA0/rick-rolld' - verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0' - end + get '/youtube_favorites/oHg5SJYRHA0/rick-rolld' + verify_redirect 'http://www.youtube.com/watch?v=oHg5SJYRHA0' end def test_openid - with_test_routes do - get '/openid/login' - assert_equal 'openid#login', @response.body + get '/openid/login' + assert_equal 'openid#login', @response.body - post '/openid/login' - assert_equal 'openid#login', @response.body - end + post '/openid/login' + assert_equal 'openid#login', @response.body end def test_bookmarks - with_test_routes do - get '/bookmark/build' - assert_equal 'bookmarks#new', @response.body - assert_equal '/bookmark/build', bookmark_new_path - - post '/bookmark/create' - assert_equal 'bookmarks#create', @response.body - assert_equal '/bookmark/create', bookmark_path - - put '/bookmark/update' - assert_equal 'bookmarks#update', @response.body - assert_equal '/bookmark/update', bookmark_update_path - - get '/bookmark/remove' - assert_equal 'bookmarks#destroy', @response.body - assert_equal '/bookmark/remove', bookmark_remove_path - end + get '/bookmark/build' + assert_equal 'bookmarks#new', @response.body + assert_equal '/bookmark/build', bookmark_new_path + + post '/bookmark/create' + assert_equal 'bookmarks#create', @response.body + assert_equal '/bookmark/create', bookmark_path + + put '/bookmark/update' + assert_equal 'bookmarks#update', @response.body + assert_equal '/bookmark/update', bookmark_update_path + + get '/bookmark/remove' + assert_equal 'bookmarks#destroy', @response.body + assert_equal '/bookmark/remove', bookmark_remove_path end def test_pagemarks - with_test_routes do - get '/pagemark/build' - assert_equal 'pagemarks#new', @response.body - assert_equal '/pagemark/build', pagemark_new_path - - post '/pagemark/create' - assert_equal 'pagemarks#create', @response.body - assert_equal '/pagemark/create', pagemark_path - - put '/pagemark/update' - assert_equal 'pagemarks#update', @response.body - assert_equal '/pagemark/update', pagemark_update_path - - get '/pagemark/remove' - assert_equal 'pagemarks#destroy', @response.body - assert_equal '/pagemark/remove', pagemark_remove_path - end + get '/pagemark/build' + assert_equal 'pagemarks#new', @response.body + assert_equal '/pagemark/build', pagemark_new_path + + post '/pagemark/create' + assert_equal 'pagemarks#create', @response.body + assert_equal '/pagemark/create', pagemark_path + + put '/pagemark/update' + assert_equal 'pagemarks#update', @response.body + assert_equal '/pagemark/update', pagemark_update_path + + get '/pagemark/remove' + assert_equal 'pagemarks#destroy', @response.body + assert_equal '/pagemark/remove', pagemark_remove_path end def test_admin - with_test_routes do - get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} - assert_equal 'queenbee#index', @response.body + get '/admin', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#index', @response.body - get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} - assert_equal 'pass', @response.headers['X-Cascade'] + get '/admin', {}, {'REMOTE_ADDR' => '10.0.0.100'} + assert_equal 'pass', @response.headers['X-Cascade'] - get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} - assert_equal 'queenbee#accounts', @response.body + get '/admin/accounts', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#accounts', @response.body - get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} - assert_equal 'pass', @response.headers['X-Cascade'] + get '/admin/accounts', {}, {'REMOTE_ADDR' => '10.0.0.100'} + assert_equal 'pass', @response.headers['X-Cascade'] - get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'} - assert_equal 'queenbee#passwords', @response.body + get '/admin/passwords', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'queenbee#passwords', @response.body - get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'} - assert_equal 'pass', @response.headers['X-Cascade'] - end + get '/admin/passwords', {}, {'REMOTE_ADDR' => '10.0.0.100'} + assert_equal 'pass', @response.headers['X-Cascade'] end def test_global - with_test_routes do - get '/global/dashboard' - assert_equal 'global#dashboard', @response.body + get '/global/dashboard' + assert_equal 'global#dashboard', @response.body - get '/global/export' - assert_equal 'global#export', @response.body + get '/global/export' + assert_equal 'global#export', @response.body - get '/global/hide_notice' - assert_equal 'global#hide_notice', @response.body + get '/global/hide_notice' + assert_equal 'global#hide_notice', @response.body - get '/export/123/foo.txt' - assert_equal 'global#export', @response.body + get '/export/123/foo.txt' + assert_equal 'global#export', @response.body - assert_equal '/global/export', export_request_path - assert_equal '/global/hide_notice', global_hide_notice_path - assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt') - end + assert_equal '/global/export', export_request_path + assert_equal '/global/hide_notice', global_hide_notice_path + assert_equal '/export/123/foo.txt', export_download_path(:id => 123, :file => 'foo.txt') end def test_local - with_test_routes do - get '/local/dashboard' - assert_equal 'local#dashboard', @response.body - end + get '/local/dashboard' + assert_equal 'local#dashboard', @response.body end # tests the use of dup in url_for @@ -850,648 +814,561 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end def test_projects_status - with_test_routes do - assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true) - assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true) - end + assert_equal '/projects/status', url_for(:controller => 'projects', :action => 'status', :only_path => true) + assert_equal '/projects/status.json', url_for(:controller => 'projects', :action => 'status', :format => 'json', :only_path => true) end def test_projects - with_test_routes do - get '/projects' - assert_equal 'project#index', @response.body - assert_equal '/projects', projects_path + get '/projects' + assert_equal 'project#index', @response.body + assert_equal '/projects', projects_path - post '/projects' - assert_equal 'project#create', @response.body + post '/projects' + assert_equal 'project#create', @response.body - get '/projects.xml' - assert_equal 'project#index', @response.body - assert_equal '/projects.xml', projects_path(:format => 'xml') + get '/projects.xml' + assert_equal 'project#index', @response.body + assert_equal '/projects.xml', projects_path(:format => 'xml') - get '/projects/new' - assert_equal 'project#new', @response.body - assert_equal '/projects/new', new_project_path + get '/projects/new' + assert_equal 'project#new', @response.body + assert_equal '/projects/new', new_project_path - get '/projects/new.xml' - assert_equal 'project#new', @response.body - assert_equal '/projects/new.xml', new_project_path(:format => 'xml') + get '/projects/new.xml' + assert_equal 'project#new', @response.body + assert_equal '/projects/new.xml', new_project_path(:format => 'xml') - get '/projects/1' - assert_equal 'project#show', @response.body - assert_equal '/projects/1', project_path(:id => '1') + get '/projects/1' + assert_equal 'project#show', @response.body + assert_equal '/projects/1', project_path(:id => '1') - get '/projects/1.xml' - assert_equal 'project#show', @response.body - assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') + get '/projects/1.xml' + assert_equal 'project#show', @response.body + assert_equal '/projects/1.xml', project_path(:id => '1', :format => 'xml') - get '/projects/1/edit' - assert_equal 'project#edit', @response.body - assert_equal '/projects/1/edit', edit_project_path(:id => '1') - end + get '/projects/1/edit' + assert_equal 'project#edit', @response.body + assert_equal '/projects/1/edit', edit_project_path(:id => '1') end def test_projects_involvements - with_test_routes do - get '/projects/1/involvements' - assert_equal 'involvements#index', @response.body - assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1') + get '/projects/1/involvements' + assert_equal 'involvements#index', @response.body + assert_equal '/projects/1/involvements', project_involvements_path(:project_id => '1') - get '/projects/1/involvements/new' - assert_equal 'involvements#new', @response.body - assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1') + get '/projects/1/involvements/new' + assert_equal 'involvements#new', @response.body + assert_equal '/projects/1/involvements/new', new_project_involvement_path(:project_id => '1') - get '/projects/1/involvements/1' - assert_equal 'involvements#show', @response.body - assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1') + get '/projects/1/involvements/1' + assert_equal 'involvements#show', @response.body + assert_equal '/projects/1/involvements/1', project_involvement_path(:project_id => '1', :id => '1') - put '/projects/1/involvements/1' - assert_equal 'involvements#update', @response.body + put '/projects/1/involvements/1' + assert_equal 'involvements#update', @response.body - delete '/projects/1/involvements/1' - assert_equal 'involvements#destroy', @response.body + delete '/projects/1/involvements/1' + assert_equal 'involvements#destroy', @response.body - get '/projects/1/involvements/1/edit' - assert_equal 'involvements#edit', @response.body - assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1') - end + get '/projects/1/involvements/1/edit' + assert_equal 'involvements#edit', @response.body + assert_equal '/projects/1/involvements/1/edit', edit_project_involvement_path(:project_id => '1', :id => '1') end def test_projects_attachments - with_test_routes do - get '/projects/1/attachments' - assert_equal 'attachments#index', @response.body - assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1') - end + get '/projects/1/attachments' + assert_equal 'attachments#index', @response.body + assert_equal '/projects/1/attachments', project_attachments_path(:project_id => '1') end def test_projects_participants - with_test_routes do - get '/projects/1/participants' - assert_equal 'participants#index', @response.body - assert_equal '/projects/1/participants', project_participants_path(:project_id => '1') - - put '/projects/1/participants/update_all' - assert_equal 'participants#update_all', @response.body - assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') - end + get '/projects/1/participants' + assert_equal 'participants#index', @response.body + assert_equal '/projects/1/participants', project_participants_path(:project_id => '1') + + put '/projects/1/participants/update_all' + assert_equal 'participants#update_all', @response.body + assert_equal '/projects/1/participants/update_all', update_all_project_participants_path(:project_id => '1') end def test_projects_companies - with_test_routes do - get '/projects/1/companies' - assert_equal 'companies#index', @response.body - assert_equal '/projects/1/companies', project_companies_path(:project_id => '1') - - get '/projects/1/companies/1/people' - assert_equal 'people#index', @response.body - assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') - - get '/projects/1/companies/1/avatar' - assert_equal 'avatar#show', @response.body - assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') - end + get '/projects/1/companies' + assert_equal 'companies#index', @response.body + assert_equal '/projects/1/companies', project_companies_path(:project_id => '1') + + get '/projects/1/companies/1/people' + assert_equal 'people#index', @response.body + assert_equal '/projects/1/companies/1/people', project_company_people_path(:project_id => '1', :company_id => '1') + + get '/projects/1/companies/1/avatar' + assert_equal 'avatar#show', @response.body + assert_equal '/projects/1/companies/1/avatar', project_company_avatar_path(:project_id => '1', :company_id => '1') end def test_project_manager - with_test_routes do - get '/projects/1/manager' - assert_equal 'managers#show', @response.body - assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1') - - get '/projects/1/manager/new' - assert_equal 'managers#new', @response.body - assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1') - - post '/projects/1/manager/fire' - assert_equal 'managers#fire', @response.body - assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1') - end + get '/projects/1/manager' + assert_equal 'managers#show', @response.body + assert_equal '/projects/1/manager', project_super_manager_path(:project_id => '1') + + get '/projects/1/manager/new' + assert_equal 'managers#new', @response.body + assert_equal '/projects/1/manager/new', new_project_super_manager_path(:project_id => '1') + + post '/projects/1/manager/fire' + assert_equal 'managers#fire', @response.body + assert_equal '/projects/1/manager/fire', fire_project_super_manager_path(:project_id => '1') end def test_project_images - with_test_routes do - get '/projects/1/images' - assert_equal 'images#index', @response.body - assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1') - - get '/projects/1/images/new' - assert_equal 'images#new', @response.body - assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1') - - post '/projects/1/images/1/revise' - assert_equal 'images#revise', @response.body - assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1') - end + get '/projects/1/images' + assert_equal 'images#index', @response.body + assert_equal '/projects/1/images', project_funny_images_path(:project_id => '1') + + get '/projects/1/images/new' + assert_equal 'images#new', @response.body + assert_equal '/projects/1/images/new', new_project_funny_image_path(:project_id => '1') + + post '/projects/1/images/1/revise' + assert_equal 'images#revise', @response.body + assert_equal '/projects/1/images/1/revise', revise_project_funny_image_path(:project_id => '1', :id => '1') end def test_projects_people - with_test_routes do - get '/projects/1/people' - assert_equal 'people#index', @response.body - assert_equal '/projects/1/people', project_people_path(:project_id => '1') - - get '/projects/1/people/1' - assert_equal 'people#show', @response.body - assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1') - - get '/projects/1/people/1/7a2dec8/avatar' - assert_equal 'avatars#show', @response.body - assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') - - put '/projects/1/people/1/accessible_projects' - assert_equal 'people#accessible_projects', @response.body - assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') - - post '/projects/1/people/1/resend' - assert_equal 'people#resend', @response.body - assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') - - post '/projects/1/people/1/generate_new_password' - assert_equal 'people#generate_new_password', @response.body - assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') - end + get '/projects/1/people' + assert_equal 'people#index', @response.body + assert_equal '/projects/1/people', project_people_path(:project_id => '1') + + get '/projects/1/people/1' + assert_equal 'people#show', @response.body + assert_equal '/projects/1/people/1', project_person_path(:project_id => '1', :id => '1') + + get '/projects/1/people/1/7a2dec8/avatar' + assert_equal 'avatars#show', @response.body + assert_equal '/projects/1/people/1/7a2dec8/avatar', project_person_avatar_path(:project_id => '1', :person_id => '1', :access_token => '7a2dec8') + + put '/projects/1/people/1/accessible_projects' + assert_equal 'people#accessible_projects', @response.body + assert_equal '/projects/1/people/1/accessible_projects', accessible_projects_project_person_path(:project_id => '1', :id => '1') + + post '/projects/1/people/1/resend' + assert_equal 'people#resend', @response.body + assert_equal '/projects/1/people/1/resend', resend_project_person_path(:project_id => '1', :id => '1') + + post '/projects/1/people/1/generate_new_password' + assert_equal 'people#generate_new_password', @response.body + assert_equal '/projects/1/people/1/generate_new_password', generate_new_password_project_person_path(:project_id => '1', :id => '1') end def test_projects_with_resources_path_names - with_test_routes do - get '/projects/info_about_correlation_indexes' - assert_equal 'project#correlation_indexes', @response.body - assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path - end + get '/projects/info_about_correlation_indexes' + assert_equal 'project#correlation_indexes', @response.body + assert_equal '/projects/info_about_correlation_indexes', correlation_indexes_projects_path end def test_projects_posts - with_test_routes do - get '/projects/1/posts' - assert_equal 'posts#index', @response.body - assert_equal '/projects/1/posts', project_posts_path(:project_id => '1') - - get '/projects/1/posts/archive' - assert_equal 'posts#archive', @response.body - assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') - - get '/projects/1/posts/toggle_view' - assert_equal 'posts#toggle_view', @response.body - assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') - - post '/projects/1/posts/1/preview' - assert_equal 'posts#preview', @response.body - assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') - - get '/projects/1/posts/1/subscription' - assert_equal 'subscriptions#show', @response.body - assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') - - get '/projects/1/posts/1/comments' - assert_equal 'comments#index', @response.body - assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') - - post '/projects/1/posts/1/comments/preview' - assert_equal 'comments#preview', @response.body - assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') - end + get '/projects/1/posts' + assert_equal 'posts#index', @response.body + assert_equal '/projects/1/posts', project_posts_path(:project_id => '1') + + get '/projects/1/posts/archive' + assert_equal 'posts#archive', @response.body + assert_equal '/projects/1/posts/archive', archive_project_posts_path(:project_id => '1') + + get '/projects/1/posts/toggle_view' + assert_equal 'posts#toggle_view', @response.body + assert_equal '/projects/1/posts/toggle_view', toggle_view_project_posts_path(:project_id => '1') + + post '/projects/1/posts/1/preview' + assert_equal 'posts#preview', @response.body + assert_equal '/projects/1/posts/1/preview', preview_project_post_path(:project_id => '1', :id => '1') + + get '/projects/1/posts/1/subscription' + assert_equal 'subscriptions#show', @response.body + assert_equal '/projects/1/posts/1/subscription', project_post_subscription_path(:project_id => '1', :post_id => '1') + + get '/projects/1/posts/1/comments' + assert_equal 'comments#index', @response.body + assert_equal '/projects/1/posts/1/comments', project_post_comments_path(:project_id => '1', :post_id => '1') + + post '/projects/1/posts/1/comments/preview' + assert_equal 'comments#preview', @response.body + assert_equal '/projects/1/posts/1/comments/preview', preview_project_post_comments_path(:project_id => '1', :post_id => '1') end def test_replies - with_test_routes do - put '/replies/1/answer' - assert_equal 'replies#mark_as_answer', @response.body + put '/replies/1/answer' + assert_equal 'replies#mark_as_answer', @response.body - delete '/replies/1/answer' - assert_equal 'replies#unmark_as_answer', @response.body - end + delete '/replies/1/answer' + assert_equal 'replies#unmark_as_answer', @response.body end def test_resource_routes_with_only_and_except - with_test_routes do - get '/posts' - assert_equal 'posts#index', @response.body - assert_equal '/posts', posts_path - - get '/posts/1' - assert_equal 'posts#show', @response.body - assert_equal '/posts/1', post_path(:id => 1) - - get '/posts/1/comments' - assert_equal 'comments#index', @response.body - assert_equal '/posts/1/comments', post_comments_path(:post_id => 1) - - post '/posts' - assert_equal 'pass', @response.headers['X-Cascade'] - put '/posts/1' - assert_equal 'pass', @response.headers['X-Cascade'] - delete '/posts/1' - assert_equal 'pass', @response.headers['X-Cascade'] - delete '/posts/1/comments' - assert_equal 'pass', @response.headers['X-Cascade'] - end + get '/posts' + assert_equal 'posts#index', @response.body + assert_equal '/posts', posts_path + + get '/posts/1' + assert_equal 'posts#show', @response.body + assert_equal '/posts/1', post_path(:id => 1) + + get '/posts/1/comments' + assert_equal 'comments#index', @response.body + assert_equal '/posts/1/comments', post_comments_path(:post_id => 1) + + post '/posts' + assert_equal 'pass', @response.headers['X-Cascade'] + put '/posts/1' + assert_equal 'pass', @response.headers['X-Cascade'] + delete '/posts/1' + assert_equal 'pass', @response.headers['X-Cascade'] + delete '/posts/1/comments' + assert_equal 'pass', @response.headers['X-Cascade'] end def test_resource_routes_only_create_update_destroy - with_test_routes do - delete '/past' - assert_equal 'pasts#destroy', @response.body - assert_equal '/past', past_path - - put '/present' - assert_equal 'presents#update', @response.body - assert_equal '/present', present_path - - post '/future' - assert_equal 'futures#create', @response.body - assert_equal '/future', future_path - end + delete '/past' + assert_equal 'pasts#destroy', @response.body + assert_equal '/past', past_path + + patch '/present' + assert_equal 'presents#update', @response.body + assert_equal '/present', present_path + + put '/present' + assert_equal 'presents#update', @response.body + assert_equal '/present', present_path + + post '/future' + assert_equal 'futures#create', @response.body + assert_equal '/future', future_path end def test_resources_routes_only_create_update_destroy - with_test_routes do - post '/relationships' - assert_equal 'relationships#create', @response.body - assert_equal '/relationships', relationships_path - - delete '/relationships/1' - assert_equal 'relationships#destroy', @response.body - assert_equal '/relationships/1', relationship_path(1) - - put '/friendships/1' - assert_equal 'friendships#update', @response.body - assert_equal '/friendships/1', friendship_path(1) - end + post '/relationships' + assert_equal 'relationships#create', @response.body + assert_equal '/relationships', relationships_path + + delete '/relationships/1' + assert_equal 'relationships#destroy', @response.body + assert_equal '/relationships/1', relationship_path(1) + + patch '/friendships/1' + assert_equal 'friendships#update', @response.body + assert_equal '/friendships/1', friendship_path(1) + + put '/friendships/1' + assert_equal 'friendships#update', @response.body + assert_equal '/friendships/1', friendship_path(1) end def test_resource_with_slugs_in_ids - with_test_routes do - get '/posts/rails-rocks' - assert_equal 'posts#show', @response.body - assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks') - end + get '/posts/rails-rocks' + assert_equal 'posts#show', @response.body + assert_equal '/posts/rails-rocks', post_path(:id => 'rails-rocks') end def test_resources_for_uncountable_names - with_test_routes do - assert_equal '/sheep', sheep_index_path - assert_equal '/sheep/1', sheep_path(1) - assert_equal '/sheep/new', new_sheep_path - assert_equal '/sheep/1/edit', edit_sheep_path(1) - assert_equal '/sheep/1/_it', _it_sheep_path(1) - end + assert_equal '/sheep', sheep_index_path + assert_equal '/sheep/1', sheep_path(1) + assert_equal '/sheep/new', new_sheep_path + assert_equal '/sheep/1/edit', edit_sheep_path(1) + assert_equal '/sheep/1/_it', _it_sheep_path(1) end def test_path_names - with_test_routes do - get '/pt/projetos' - assert_equal 'projects#index', @response.body - assert_equal '/pt/projetos', pt_projects_path - - get '/pt/projetos/1/editar' - assert_equal 'projects#edit', @response.body - assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1) - - get '/pt/administrador' - assert_equal 'admins#show', @response.body - assert_equal '/pt/administrador', pt_admin_path - - get '/pt/administrador/novo' - assert_equal 'admins#new', @response.body - assert_equal '/pt/administrador/novo', new_pt_admin_path - - put '/pt/administrador/ativar' - assert_equal 'admins#activate', @response.body - assert_equal '/pt/administrador/ativar', activate_pt_admin_path - end + get '/pt/projetos' + assert_equal 'projects#index', @response.body + assert_equal '/pt/projetos', pt_projects_path + + get '/pt/projetos/1/editar' + assert_equal 'projects#edit', @response.body + assert_equal '/pt/projetos/1/editar', edit_pt_project_path(1) + + get '/pt/administrador' + assert_equal 'admins#show', @response.body + assert_equal '/pt/administrador', pt_admin_path + + get '/pt/administrador/novo' + assert_equal 'admins#new', @response.body + assert_equal '/pt/administrador/novo', new_pt_admin_path + + put '/pt/administrador/ativar' + assert_equal 'admins#activate', @response.body + assert_equal '/pt/administrador/ativar', activate_pt_admin_path end def test_path_option_override - with_test_routes do - get '/pt/projetos/novo/abrir' - assert_equal 'projects#open', @response.body - assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path - - put '/pt/projetos/1/fechar' - assert_equal 'projects#close', @response.body - assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1) - end + get '/pt/projetos/novo/abrir' + assert_equal 'projects#open', @response.body + assert_equal '/pt/projetos/novo/abrir', open_new_pt_project_path + + put '/pt/projetos/1/fechar' + assert_equal 'projects#close', @response.body + assert_equal '/pt/projetos/1/fechar', close_pt_project_path(1) end def test_sprockets - with_test_routes do - get '/sprockets.js' - assert_equal 'javascripts', @response.body - end + get '/sprockets.js' + assert_equal 'javascripts', @response.body end def test_update_person_route - with_test_routes do - get '/people/1/update' - assert_equal 'people#update', @response.body + get '/people/1/update' + assert_equal 'people#update', @response.body - assert_equal '/people/1/update', update_person_path(:id => 1) - end + assert_equal '/people/1/update', update_person_path(:id => 1) end def test_update_project_person - with_test_routes do - get '/projects/1/people/2/update' - assert_equal 'people#update', @response.body + get '/projects/1/people/2/update' + assert_equal 'people#update', @response.body - assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2) - end + assert_equal '/projects/1/people/2/update', update_project_person_path(:project_id => 1, :id => 2) end def test_forum_products - with_test_routes do - get '/forum' - assert_equal 'forum/products#index', @response.body - assert_equal '/forum', forum_products_path - - get '/forum/basecamp' - assert_equal 'forum/products#show', @response.body - assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp') - - get '/forum/basecamp/questions' - assert_equal 'forum/questions#index', @response.body - assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp') - - get '/forum/basecamp/questions/1' - assert_equal 'forum/questions#show', @response.body - assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1) - end + get '/forum' + assert_equal 'forum/products#index', @response.body + assert_equal '/forum', forum_products_path + + get '/forum/basecamp' + assert_equal 'forum/products#show', @response.body + assert_equal '/forum/basecamp', forum_product_path(:id => 'basecamp') + + get '/forum/basecamp/questions' + assert_equal 'forum/questions#index', @response.body + assert_equal '/forum/basecamp/questions', forum_product_questions_path(:product_id => 'basecamp') + + get '/forum/basecamp/questions/1' + assert_equal 'forum/questions#show', @response.body + assert_equal '/forum/basecamp/questions/1', forum_product_question_path(:product_id => 'basecamp', :id => 1) end def test_articles_perma - with_test_routes do - get '/articles/2009/08/18/rails-3' - assert_equal 'articles#show', @response.body + get '/articles/2009/08/18/rails-3' + assert_equal 'articles#show', @response.body - assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3') - end + assert_equal '/articles/2009/8/18/rails-3', article_path(:year => 2009, :month => 8, :day => 18, :title => 'rails-3') end def test_account_namespace - with_test_routes do - get '/account/subscription' - assert_equal 'account/subscriptions#show', @response.body - assert_equal '/account/subscription', account_subscription_path - - get '/account/credit' - assert_equal 'account/credits#show', @response.body - assert_equal '/account/credit', account_credit_path - - get '/account/credit_card' - assert_equal 'account/credit_cards#show', @response.body - assert_equal '/account/credit_card', account_credit_card_path - end + get '/account/subscription' + assert_equal 'account/subscriptions#show', @response.body + assert_equal '/account/subscription', account_subscription_path + + get '/account/credit' + assert_equal 'account/credits#show', @response.body + assert_equal '/account/credit', account_credit_path + + get '/account/credit_card' + assert_equal 'account/credit_cards#show', @response.body + assert_equal '/account/credit_card', account_credit_card_path end def test_nested_namespace - with_test_routes do - get '/account/admin/subscription' - assert_equal 'account/admin/subscriptions#show', @response.body - assert_equal '/account/admin/subscription', account_admin_subscription_path - end + get '/account/admin/subscription' + assert_equal 'account/admin/subscriptions#show', @response.body + assert_equal '/account/admin/subscription', account_admin_subscription_path end def test_namespace_nested_in_resources - with_test_routes do - get '/clients/1/google/account' - assert_equal '/clients/1/google/account', client_google_account_path(1) - assert_equal 'google/accounts#show', @response.body - - get '/clients/1/google/account/secret/info' - assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1) - assert_equal 'google/secret/infos#show', @response.body - end + get '/clients/1/google/account' + assert_equal '/clients/1/google/account', client_google_account_path(1) + assert_equal 'google/accounts#show', @response.body + + get '/clients/1/google/account/secret/info' + assert_equal '/clients/1/google/account/secret/info', client_google_account_secret_info_path(1) + assert_equal 'google/secret/infos#show', @response.body end def test_namespace_with_options - with_test_routes do - get '/usuarios' - assert_equal '/usuarios', users_root_path - assert_equal 'users/home#index', @response.body - end + get '/usuarios' + assert_equal '/usuarios', users_root_path + assert_equal 'users/home#index', @response.body end def test_articles_with_id - with_test_routes do - get '/articles/rails/1' - assert_equal 'articles#with_id', @response.body + get '/articles/rails/1' + assert_equal 'articles#with_id', @response.body - get '/articles/123/1' - assert_equal 'pass', @response.headers['X-Cascade'] + get '/articles/123/1' + assert_equal 'pass', @response.headers['X-Cascade'] - assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1) - end + assert_equal '/articles/rails/1', article_with_title_path(:title => 'rails', :id => 1) end def test_access_token_rooms - with_test_routes do - get '/12345/rooms' - assert_equal 'rooms#index', @response.body + get '/12345/rooms' + assert_equal 'rooms#index', @response.body - get '/12345/rooms/1' - assert_equal 'rooms#show', @response.body + get '/12345/rooms/1' + assert_equal 'rooms#show', @response.body - get '/12345/rooms/1/edit' - assert_equal 'rooms#edit', @response.body - end + get '/12345/rooms/1/edit' + assert_equal 'rooms#edit', @response.body end def test_root - with_test_routes do - assert_equal '/', root_path - get '/' - assert_equal 'projects#index', @response.body - end + assert_equal '/', root_path + get '/' + assert_equal 'projects#index', @response.body end def test_index - with_test_routes do - assert_equal '/info', info_path - get '/info' - assert_equal 'projects#info', @response.body - end + assert_equal '/info', info_path + get '/info' + assert_equal 'projects#info', @response.body end def test_match_shorthand_with_no_scope - with_test_routes do - assert_equal '/account/overview', account_overview_path - get '/account/overview' - assert_equal 'account#overview', @response.body - end + assert_equal '/account/overview', account_overview_path + get '/account/overview' + assert_equal 'account#overview', @response.body end def test_match_shorthand_inside_namespace - with_test_routes do - assert_equal '/account/shorthand', account_shorthand_path - get '/account/shorthand' - assert_equal 'account#shorthand', @response.body - end + assert_equal '/account/shorthand', account_shorthand_path + get '/account/shorthand' + assert_equal 'account#shorthand', @response.body end def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper - with_test_routes do - assert_equal '/replies', replies_path - end + assert_equal '/replies', replies_path end def test_scoped_controller_with_namespace_and_action - with_test_routes do - assert_equal '/account/twitter/callback', account_callback_path("twitter") - get '/account/twitter/callback' - assert_equal 'account/callbacks#twitter', @response.body + assert_equal '/account/twitter/callback', account_callback_path("twitter") + get '/account/twitter/callback' + assert_equal 'account/callbacks#twitter', @response.body - get '/account/whatever/callback' - assert_equal 'Not Found', @response.body - end + get '/account/whatever/callback' + assert_equal 'Not Found', @response.body end def test_convention_match_nested_and_with_leading_slash - with_test_routes do - assert_equal '/account/nested/overview', account_nested_overview_path - get '/account/nested/overview' - assert_equal 'account/nested#overview', @response.body - end + assert_equal '/account/nested/overview', account_nested_overview_path + get '/account/nested/overview' + assert_equal 'account/nested#overview', @response.body end def test_convention_with_explicit_end - with_test_routes do - get '/sign_in' - assert_equal 'sessions#new', @response.body - assert_equal '/sign_in', sign_in_path - end + get '/sign_in' + assert_equal 'sessions#new', @response.body + assert_equal '/sign_in', sign_in_path end def test_redirect_with_complete_url_and_status - with_test_routes do - get '/account/google' - verify_redirect 'http://www.google.com/', 302 - end + get '/account/google' + verify_redirect 'http://www.google.com/', 302 end def test_redirect_with_port previous_host, self.host = self.host, 'www.example.com:3000' - with_test_routes do - get '/account/login' - verify_redirect 'http://www.example.com:3000/login' - end + + get '/account/login' + verify_redirect 'http://www.example.com:3000/login' ensure self.host = previous_host end def test_normalize_namespaced_matches - with_test_routes do - assert_equal '/account/description', account_description_path + assert_equal '/account/description', account_description_path - get '/account/description' - assert_equal 'account#description', @response.body - end + get '/account/description' + assert_equal 'account#description', @response.body end def test_namespaced_roots - with_test_routes do - assert_equal '/account', account_root_path - get '/account' - assert_equal 'account/account#index', @response.body - end + assert_equal '/account', account_root_path + get '/account' + assert_equal 'account/account#index', @response.body end def test_optional_scoped_root - with_test_routes do - assert_equal '/en', root_path("en") - get '/en' - assert_equal 'projects#index', @response.body - end + assert_equal '/en', root_path("en") + get '/en' + assert_equal 'projects#index', @response.body end def test_optional_scoped_path - with_test_routes do - assert_equal '/en/descriptions', descriptions_path("en") - assert_equal '/descriptions', descriptions_path(nil) - assert_equal '/en/descriptions/1', description_path("en", 1) - assert_equal '/descriptions/1', description_path(nil, 1) + assert_equal '/en/descriptions', descriptions_path("en") + assert_equal '/descriptions', descriptions_path(nil) + assert_equal '/en/descriptions/1', description_path("en", 1) + assert_equal '/descriptions/1', description_path(nil, 1) - get '/en/descriptions' - assert_equal 'descriptions#index', @response.body + get '/en/descriptions' + assert_equal 'descriptions#index', @response.body - get '/descriptions' - assert_equal 'descriptions#index', @response.body + get '/descriptions' + assert_equal 'descriptions#index', @response.body - get '/en/descriptions/1' - assert_equal 'descriptions#show', @response.body + get '/en/descriptions/1' + assert_equal 'descriptions#show', @response.body - get '/descriptions/1' - assert_equal 'descriptions#show', @response.body - end + get '/descriptions/1' + assert_equal 'descriptions#show', @response.body end def test_nested_optional_scoped_path - with_test_routes do - assert_equal '/admin/en/descriptions', admin_descriptions_path("en") - assert_equal '/admin/descriptions', admin_descriptions_path(nil) - assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1) - assert_equal '/admin/descriptions/1', admin_description_path(nil, 1) + assert_equal '/admin/en/descriptions', admin_descriptions_path("en") + assert_equal '/admin/descriptions', admin_descriptions_path(nil) + assert_equal '/admin/en/descriptions/1', admin_description_path("en", 1) + assert_equal '/admin/descriptions/1', admin_description_path(nil, 1) - get '/admin/en/descriptions' - assert_equal 'admin/descriptions#index', @response.body + get '/admin/en/descriptions' + assert_equal 'admin/descriptions#index', @response.body - get '/admin/descriptions' - assert_equal 'admin/descriptions#index', @response.body + get '/admin/descriptions' + assert_equal 'admin/descriptions#index', @response.body - get '/admin/en/descriptions/1' - assert_equal 'admin/descriptions#show', @response.body + get '/admin/en/descriptions/1' + assert_equal 'admin/descriptions#show', @response.body - get '/admin/descriptions/1' - assert_equal 'admin/descriptions#show', @response.body - end + get '/admin/descriptions/1' + assert_equal 'admin/descriptions#show', @response.body end def test_nested_optional_path_shorthand - with_test_routes do - get '/registrations/new' - assert_nil @request.params[:locale] + get '/registrations/new' + assert_nil @request.params[:locale] - get '/en/registrations/new' - assert_equal 'en', @request.params[:locale] - end + get '/en/registrations/new' + assert_equal 'en', @request.params[:locale] end def test_default_params - with_test_routes do - get '/inline_pages' - assert_equal 'home', @request.params[:id] + get '/inline_pages' + assert_equal 'home', @request.params[:id] - get '/default_pages' - assert_equal 'home', @request.params[:id] + get '/default_pages' + assert_equal 'home', @request.params[:id] - get '/scoped_pages' - assert_equal 'home', @request.params[:id] - end + get '/scoped_pages' + assert_equal 'home', @request.params[:id] end def test_resource_constraints - with_test_routes do - get '/products/1' - assert_equal 'pass', @response.headers['X-Cascade'] - get '/products' - assert_equal 'products#root', @response.body - get '/products/favorite' - assert_equal 'products#favorite', @response.body - get '/products/0001' - assert_equal 'products#show', @response.body - - get '/products/1/images' - assert_equal 'pass', @response.headers['X-Cascade'] - get '/products/0001/images' - assert_equal 'images#index', @response.body - get '/products/0001/images/0001' - assert_equal 'images#show', @response.body - - get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} - assert_equal 'pass', @response.headers['X-Cascade'] - get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'} - assert_equal 'dashboards#show', @response.body - end + get '/products/1' + assert_equal 'pass', @response.headers['X-Cascade'] + get '/products' + assert_equal 'products#root', @response.body + get '/products/favorite' + assert_equal 'products#favorite', @response.body + get '/products/0001' + assert_equal 'products#show', @response.body + + get '/products/1/images' + assert_equal 'pass', @response.headers['X-Cascade'] + get '/products/0001/images' + assert_equal 'images#index', @response.body + get '/products/0001/images/0001' + assert_equal 'images#show', @response.body + + get '/dashboard', {}, {'REMOTE_ADDR' => '10.0.0.100'} + assert_equal 'pass', @response.headers['X-Cascade'] + get '/dashboard', {}, {'REMOTE_ADDR' => '192.168.1.100'} + assert_equal 'dashboards#show', @response.body end def test_root_works_in_the_resources_scope @@ -1501,731 +1378,651 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest end def test_module_scope - with_test_routes do - get '/token' - assert_equal 'api/tokens#show', @response.body - assert_equal '/token', token_path - end + get '/token' + assert_equal 'api/tokens#show', @response.body + assert_equal '/token', token_path end def test_path_scope - with_test_routes do - get '/api/me' - assert_equal 'mes#show', @response.body - assert_equal '/api/me', me_path + get '/api/me' + assert_equal 'mes#show', @response.body + assert_equal '/api/me', me_path - get '/api' - assert_equal 'mes#index', @response.body - end + get '/api' + assert_equal 'mes#index', @response.body end def test_url_generator_for_generic_route - with_test_routes do - get 'whatever/foo/bar' - assert_equal 'foo#bar', @response.body + get 'whatever/foo/bar' + assert_equal 'foo#bar', @response.body - assert_equal 'http://www.example.com/whatever/foo/bar/1', - url_for(:controller => "foo", :action => "bar", :id => 1) - end + assert_equal 'http://www.example.com/whatever/foo/bar/1', + url_for(:controller => "foo", :action => "bar", :id => 1) end def test_url_generator_for_namespaced_generic_route - with_test_routes do - get 'whatever/foo/bar/show' - assert_equal 'foo/bar#show', @response.body + get 'whatever/foo/bar/show' + assert_equal 'foo/bar#show', @response.body - get 'whatever/foo/bar/show/1' - assert_equal 'foo/bar#show', @response.body + get 'whatever/foo/bar/show/1' + assert_equal 'foo/bar#show', @response.body - assert_equal 'http://www.example.com/whatever/foo/bar/show', - url_for(:controller => "foo/bar", :action => "show") + assert_equal 'http://www.example.com/whatever/foo/bar/show', + url_for(:controller => "foo/bar", :action => "show") - assert_equal 'http://www.example.com/whatever/foo/bar/show/1', - url_for(:controller => "foo/bar", :action => "show", :id => '1') - end + assert_equal 'http://www.example.com/whatever/foo/bar/show/1', + url_for(:controller => "foo/bar", :action => "show", :id => '1') end def test_assert_recognizes_account_overview - with_test_routes do - assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview") - end + assert_recognizes({:controller => "account", :action => "overview"}, "/account/overview") end def test_resource_new_actions - with_test_routes do - assert_equal '/replies/new/preview', preview_new_reply_path - assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path - assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path - assert_equal '/pt/products/novo/preview', preview_new_pt_product_path - assert_equal '/profile/new/preview', preview_new_profile_path + assert_equal '/replies/new/preview', preview_new_reply_path + assert_equal '/pt/projetos/novo/preview', preview_new_pt_project_path + assert_equal '/pt/administrador/novo/preview', preview_new_pt_admin_path + assert_equal '/pt/products/novo/preview', preview_new_pt_product_path + assert_equal '/profile/new/preview', preview_new_profile_path - post '/replies/new/preview' - assert_equal 'replies#preview', @response.body + post '/replies/new/preview' + assert_equal 'replies#preview', @response.body - post '/pt/projetos/novo/preview' - assert_equal 'projects#preview', @response.body + post '/pt/projetos/novo/preview' + assert_equal 'projects#preview', @response.body - post '/pt/administrador/novo/preview' - assert_equal 'admins#preview', @response.body + post '/pt/administrador/novo/preview' + assert_equal 'admins#preview', @response.body - post '/pt/products/novo/preview' - assert_equal 'products#preview', @response.body + post '/pt/products/novo/preview' + assert_equal 'products#preview', @response.body - post '/profile/new/preview' - assert_equal 'profiles#preview', @response.body - end + post '/profile/new/preview' + assert_equal 'profiles#preview', @response.body end def test_resource_merges_options_from_scope - with_test_routes do - assert_raise(NameError) { new_account_path } + assert_raise(NameError) { new_account_path } - get '/account/new' - assert_equal 404, status - end + get '/account/new' + assert_equal 404, status end def test_resources_merges_options_from_scope - with_test_routes do - assert_raise(NoMethodError) { edit_product_path('1') } + assert_raise(NoMethodError) { edit_product_path('1') } - get '/products/1/edit' - assert_equal 404, status + get '/products/1/edit' + assert_equal 404, status - assert_raise(NoMethodError) { edit_product_image_path('1', '2') } + assert_raise(NoMethodError) { edit_product_image_path('1', '2') } - post '/products/1/images/2/edit' - assert_equal 404, status - end + post '/products/1/images/2/edit' + assert_equal 404, status end def test_shallow_nested_resources - with_test_routes do + get '/api/teams' + assert_equal 'api/teams#index', @response.body + assert_equal '/api/teams', api_teams_path - get '/api/teams' - assert_equal 'api/teams#index', @response.body - assert_equal '/api/teams', api_teams_path + get '/api/teams/new' + assert_equal 'api/teams#new', @response.body + assert_equal '/api/teams/new', new_api_team_path - get '/api/teams/new' - assert_equal 'api/teams#new', @response.body - assert_equal '/api/teams/new', new_api_team_path + get '/api/teams/1' + assert_equal 'api/teams#show', @response.body + assert_equal '/api/teams/1', api_team_path(:id => '1') - get '/api/teams/1' - assert_equal 'api/teams#show', @response.body - assert_equal '/api/teams/1', api_team_path(:id => '1') + get '/api/teams/1/edit' + assert_equal 'api/teams#edit', @response.body + assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1') - get '/api/teams/1/edit' - assert_equal 'api/teams#edit', @response.body - assert_equal '/api/teams/1/edit', edit_api_team_path(:id => '1') + get '/api/teams/1/players' + assert_equal 'api/players#index', @response.body + assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1') - get '/api/teams/1/players' - assert_equal 'api/players#index', @response.body - assert_equal '/api/teams/1/players', api_team_players_path(:team_id => '1') + get '/api/teams/1/players/new' + assert_equal 'api/players#new', @response.body + assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1') - get '/api/teams/1/players/new' - assert_equal 'api/players#new', @response.body - assert_equal '/api/teams/1/players/new', new_api_team_player_path(:team_id => '1') + get '/api/players/2' + assert_equal 'api/players#show', @response.body + assert_equal '/api/players/2', api_player_path(:id => '2') - get '/api/players/2' - assert_equal 'api/players#show', @response.body - assert_equal '/api/players/2', api_player_path(:id => '2') + get '/api/players/2/edit' + assert_equal 'api/players#edit', @response.body + assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2') - get '/api/players/2/edit' - assert_equal 'api/players#edit', @response.body - assert_equal '/api/players/2/edit', edit_api_player_path(:id => '2') + get '/api/teams/1/captain' + assert_equal 'api/captains#show', @response.body + assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1') - get '/api/teams/1/captain' - assert_equal 'api/captains#show', @response.body - assert_equal '/api/teams/1/captain', api_team_captain_path(:team_id => '1') + get '/api/teams/1/captain/new' + assert_equal 'api/captains#new', @response.body + assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1') - get '/api/teams/1/captain/new' - assert_equal 'api/captains#new', @response.body - assert_equal '/api/teams/1/captain/new', new_api_team_captain_path(:team_id => '1') + get '/api/teams/1/captain/edit' + assert_equal 'api/captains#edit', @response.body + assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1') - get '/api/teams/1/captain/edit' - assert_equal 'api/captains#edit', @response.body - assert_equal '/api/teams/1/captain/edit', edit_api_team_captain_path(:team_id => '1') + get '/threads' + assert_equal 'threads#index', @response.body + assert_equal '/threads', threads_path - get '/threads' - assert_equal 'threads#index', @response.body - assert_equal '/threads', threads_path + get '/threads/new' + assert_equal 'threads#new', @response.body + assert_equal '/threads/new', new_thread_path - get '/threads/new' - assert_equal 'threads#new', @response.body - assert_equal '/threads/new', new_thread_path + get '/threads/1' + assert_equal 'threads#show', @response.body + assert_equal '/threads/1', thread_path(:id => '1') - get '/threads/1' - assert_equal 'threads#show', @response.body - assert_equal '/threads/1', thread_path(:id => '1') + get '/threads/1/edit' + assert_equal 'threads#edit', @response.body + assert_equal '/threads/1/edit', edit_thread_path(:id => '1') - get '/threads/1/edit' - assert_equal 'threads#edit', @response.body - assert_equal '/threads/1/edit', edit_thread_path(:id => '1') + get '/threads/1/owner' + assert_equal 'owners#show', @response.body + assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1') - get '/threads/1/owner' - assert_equal 'owners#show', @response.body - assert_equal '/threads/1/owner', thread_owner_path(:thread_id => '1') + get '/threads/1/messages' + assert_equal 'messages#index', @response.body + assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1') - get '/threads/1/messages' - assert_equal 'messages#index', @response.body - assert_equal '/threads/1/messages', thread_messages_path(:thread_id => '1') + get '/threads/1/messages/new' + assert_equal 'messages#new', @response.body + assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1') - get '/threads/1/messages/new' - assert_equal 'messages#new', @response.body - assert_equal '/threads/1/messages/new', new_thread_message_path(:thread_id => '1') + get '/messages/2' + assert_equal 'messages#show', @response.body + assert_equal '/messages/2', message_path(:id => '2') - get '/messages/2' - assert_equal 'messages#show', @response.body - assert_equal '/messages/2', message_path(:id => '2') + get '/messages/2/edit' + assert_equal 'messages#edit', @response.body + assert_equal '/messages/2/edit', edit_message_path(:id => '2') - get '/messages/2/edit' - assert_equal 'messages#edit', @response.body - assert_equal '/messages/2/edit', edit_message_path(:id => '2') + get '/messages/2/comments' + assert_equal 'comments#index', @response.body + assert_equal '/messages/2/comments', message_comments_path(:message_id => '2') - get '/messages/2/comments' - assert_equal 'comments#index', @response.body - assert_equal '/messages/2/comments', message_comments_path(:message_id => '2') + get '/messages/2/comments/new' + assert_equal 'comments#new', @response.body + assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2') - get '/messages/2/comments/new' - assert_equal 'comments#new', @response.body - assert_equal '/messages/2/comments/new', new_message_comment_path(:message_id => '2') + get '/comments/3' + assert_equal 'comments#show', @response.body + assert_equal '/comments/3', comment_path(:id => '3') - get '/comments/3' - assert_equal 'comments#show', @response.body - assert_equal '/comments/3', comment_path(:id => '3') + get '/comments/3/edit' + assert_equal 'comments#edit', @response.body + assert_equal '/comments/3/edit', edit_comment_path(:id => '3') - get '/comments/3/edit' - assert_equal 'comments#edit', @response.body - assert_equal '/comments/3/edit', edit_comment_path(:id => '3') - - post '/comments/3/preview' - assert_equal 'comments#preview', @response.body - assert_equal '/comments/3/preview', preview_comment_path(:id => '3') - end + post '/comments/3/preview' + assert_equal 'comments#preview', @response.body + assert_equal '/comments/3/preview', preview_comment_path(:id => '3') end def test_shallow_nested_resources_within_scope - with_test_routes do + get '/hello/notes/1/trackbacks' + assert_equal 'trackbacks#index', @response.body + assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1) - get '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#index', @response.body - assert_equal '/hello/notes/1/trackbacks', note_trackbacks_path(:note_id => 1) + get '/hello/notes/1/edit' + assert_equal 'notes#edit', @response.body + assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1') - get '/hello/notes/1/edit' - assert_equal 'notes#edit', @response.body - assert_equal '/hello/notes/1/edit', edit_note_path(:id => '1') + get '/hello/notes/1/trackbacks/new' + assert_equal 'trackbacks#new', @response.body + assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1) - get '/hello/notes/1/trackbacks/new' - assert_equal 'trackbacks#new', @response.body - assert_equal '/hello/notes/1/trackbacks/new', new_note_trackback_path(:note_id => 1) + get '/hello/trackbacks/1' + assert_equal 'trackbacks#show', @response.body + assert_equal '/hello/trackbacks/1', trackback_path(:id => '1') - get '/hello/trackbacks/1' - assert_equal 'trackbacks#show', @response.body - assert_equal '/hello/trackbacks/1', trackback_path(:id => '1') + get '/hello/trackbacks/1/edit' + assert_equal 'trackbacks#edit', @response.body + assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1') - get '/hello/trackbacks/1/edit' - assert_equal 'trackbacks#edit', @response.body - assert_equal '/hello/trackbacks/1/edit', edit_trackback_path(:id => '1') + put '/hello/trackbacks/1' + assert_equal 'trackbacks#update', @response.body - put '/hello/trackbacks/1' - assert_equal 'trackbacks#update', @response.body + post '/hello/notes/1/trackbacks' + assert_equal 'trackbacks#create', @response.body - post '/hello/notes/1/trackbacks' - assert_equal 'trackbacks#create', @response.body + delete '/hello/trackbacks/1' + assert_equal 'trackbacks#destroy', @response.body - delete '/hello/trackbacks/1' - assert_equal 'trackbacks#destroy', @response.body + get '/hello/notes' + assert_equal 'notes#index', @response.body - get '/hello/notes' - assert_equal 'notes#index', @response.body + post '/hello/notes' + assert_equal 'notes#create', @response.body - post '/hello/notes' - assert_equal 'notes#create', @response.body + get '/hello/notes/new' + assert_equal 'notes#new', @response.body + assert_equal '/hello/notes/new', new_note_path - get '/hello/notes/new' - assert_equal 'notes#new', @response.body - assert_equal '/hello/notes/new', new_note_path + get '/hello/notes/1' + assert_equal 'notes#show', @response.body + assert_equal '/hello/notes/1', note_path(:id => 1) - get '/hello/notes/1' - assert_equal 'notes#show', @response.body - assert_equal '/hello/notes/1', note_path(:id => 1) + put '/hello/notes/1' + assert_equal 'notes#update', @response.body - put '/hello/notes/1' - assert_equal 'notes#update', @response.body - - delete '/hello/notes/1' - assert_equal 'notes#destroy', @response.body - end + delete '/hello/notes/1' + assert_equal 'notes#destroy', @response.body end def test_custom_resource_routes_are_scoped - with_test_routes do - assert_equal '/customers/recent', recent_customers_path - assert_equal '/customers/1/profile', profile_customer_path(:id => '1') - assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1') - assert_equal '/customers/new/preview', another_preview_new_customer_path - assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg) - assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1') - assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2') - assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1') - assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1') - assert_equal '/notes/1/print', print_note_path(:id => '1') - assert_equal '/api/customers/recent', recent_api_customers_path - assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1') - assert_equal '/api/customers/new/preview', preview_new_api_customer_path - - get '/customers/1/invoices/overdue' - assert_equal 'invoices#overdue', @response.body - - get '/customers/1/secret/profile' - assert_equal 'customers#secret', @response.body - end + assert_equal '/customers/recent', recent_customers_path + assert_equal '/customers/1/profile', profile_customer_path(:id => '1') + assert_equal '/customers/1/secret/profile', secret_profile_customer_path(:id => '1') + assert_equal '/customers/new/preview', another_preview_new_customer_path + assert_equal '/customers/1/avatar/thumbnail.jpg', thumbnail_customer_avatar_path(:customer_id => '1', :format => :jpg) + assert_equal '/customers/1/invoices/outstanding', outstanding_customer_invoices_path(:customer_id => '1') + assert_equal '/customers/1/invoices/2/print', print_customer_invoice_path(:customer_id => '1', :id => '2') + assert_equal '/customers/1/invoices/new/preview', preview_new_customer_invoice_path(:customer_id => '1') + assert_equal '/customers/1/notes/new/preview', preview_new_customer_note_path(:customer_id => '1') + assert_equal '/notes/1/print', print_note_path(:id => '1') + assert_equal '/api/customers/recent', recent_api_customers_path + assert_equal '/api/customers/1/profile', profile_api_customer_path(:id => '1') + assert_equal '/api/customers/new/preview', preview_new_api_customer_path + + get '/customers/1/invoices/overdue' + assert_equal 'invoices#overdue', @response.body + + get '/customers/1/secret/profile' + assert_equal 'customers#secret', @response.body end def test_shallow_nested_routes_ignore_module - with_test_routes do - get '/errors/1/notices' - assert_equal 'api/notices#index', @response.body - assert_equal '/errors/1/notices', error_notices_path(:error_id => '1') - - get '/notices/1' - assert_equal 'api/notices#show', @response.body - assert_equal '/notices/1', notice_path(:id => '1') - end + get '/errors/1/notices' + assert_equal 'api/notices#index', @response.body + assert_equal '/errors/1/notices', error_notices_path(:error_id => '1') + + get '/notices/1' + assert_equal 'api/notices#show', @response.body + assert_equal '/notices/1', notice_path(:id => '1') end def test_non_greedy_regexp - with_test_routes do - get '/api/1.0/users' - assert_equal 'api/users#index', @response.body - assert_equal '/api/1.0/users', api_users_path(:version => '1.0') - - get '/api/1.0/users.json' - assert_equal 'api/users#index', @response.body - assert_equal true, @request.format.json? - assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json) - - get '/api/1.0/users/first.last' - assert_equal 'api/users#show', @response.body - assert_equal 'first.last', @request.params[:id] - assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last') - - get '/api/1.0/users/first.last.xml' - assert_equal 'api/users#show', @response.body - assert_equal 'first.last', @request.params[:id] - assert_equal true, @request.format.xml? - assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml) - end + get '/api/1.0/users' + assert_equal 'api/users#index', @response.body + assert_equal '/api/1.0/users', api_users_path(:version => '1.0') + + get '/api/1.0/users.json' + assert_equal 'api/users#index', @response.body + assert_equal true, @request.format.json? + assert_equal '/api/1.0/users.json', api_users_path(:version => '1.0', :format => :json) + + get '/api/1.0/users/first.last' + assert_equal 'api/users#show', @response.body + assert_equal 'first.last', @request.params[:id] + assert_equal '/api/1.0/users/first.last', api_user_path(:version => '1.0', :id => 'first.last') + + get '/api/1.0/users/first.last.xml' + assert_equal 'api/users#show', @response.body + assert_equal 'first.last', @request.params[:id] + assert_equal true, @request.format.xml? + assert_equal '/api/1.0/users/first.last.xml', api_user_path(:version => '1.0', :id => 'first.last', :format => :xml) end def test_glob_parameter_accepts_regexp - with_test_routes do - get '/en/path/to/existing/file.html' - assert_equal 200, @response.status - end + get '/en/path/to/existing/file.html' + assert_equal 200, @response.status end def test_resources_controller_name_is_not_pluralized - with_test_routes do - get '/content' - assert_equal 'content#index', @response.body - end + get '/content' + assert_equal 'content#index', @response.body end def test_url_generator_for_optional_prefix_dynamic_segment - with_test_routes do - get '/bob/followers' - assert_equal 'followers#index', @response.body - assert_equal 'http://www.example.com/bob/followers', - url_for(:controller => "followers", :action => "index", :username => "bob") - - get '/followers' - assert_equal 'followers#index', @response.body - assert_equal 'http://www.example.com/followers', - url_for(:controller => "followers", :action => "index", :username => nil) - end + get '/bob/followers' + assert_equal 'followers#index', @response.body + assert_equal 'http://www.example.com/bob/followers', + url_for(:controller => "followers", :action => "index", :username => "bob") + + get '/followers' + assert_equal 'followers#index', @response.body + assert_equal 'http://www.example.com/followers', + url_for(:controller => "followers", :action => "index", :username => nil) end def test_url_generator_for_optional_suffix_static_and_dynamic_segment - with_test_routes do - get '/groups/user/bob' - assert_equal 'groups#index', @response.body - assert_equal 'http://www.example.com/groups/user/bob', - url_for(:controller => "groups", :action => "index", :username => "bob") - - get '/groups' - assert_equal 'groups#index', @response.body - assert_equal 'http://www.example.com/groups', - url_for(:controller => "groups", :action => "index", :username => nil) - end + get '/groups/user/bob' + assert_equal 'groups#index', @response.body + assert_equal 'http://www.example.com/groups/user/bob', + url_for(:controller => "groups", :action => "index", :username => "bob") + + get '/groups' + assert_equal 'groups#index', @response.body + assert_equal 'http://www.example.com/groups', + url_for(:controller => "groups", :action => "index", :username => nil) end def test_url_generator_for_optional_prefix_static_and_dynamic_segment - with_test_routes do - get 'user/bob/photos' - assert_equal 'photos#index', @response.body - assert_equal 'http://www.example.com/user/bob/photos', - url_for(:controller => "photos", :action => "index", :username => "bob") - - get 'photos' - assert_equal 'photos#index', @response.body - assert_equal 'http://www.example.com/photos', - url_for(:controller => "photos", :action => "index", :username => nil) - end + get 'user/bob/photos' + assert_equal 'photos#index', @response.body + assert_equal 'http://www.example.com/user/bob/photos', + url_for(:controller => "photos", :action => "index", :username => "bob") + + get 'photos' + assert_equal 'photos#index', @response.body + assert_equal 'http://www.example.com/photos', + url_for(:controller => "photos", :action => "index", :username => nil) end def test_url_recognition_for_optional_static_segments - with_test_routes do - get '/groups/discussions/messages' - assert_equal 'messages#index', @response.body + get '/groups/discussions/messages' + assert_equal 'messages#index', @response.body - get '/groups/discussions/messages/1' - assert_equal 'messages#show', @response.body + get '/groups/discussions/messages/1' + assert_equal 'messages#show', @response.body - get '/groups/messages' - assert_equal 'messages#index', @response.body + get '/groups/messages' + assert_equal 'messages#index', @response.body - get '/groups/messages/1' - assert_equal 'messages#show', @response.body + get '/groups/messages/1' + assert_equal 'messages#show', @response.body - get '/discussions/messages' - assert_equal 'messages#index', @response.body + get '/discussions/messages' + assert_equal 'messages#index', @response.body - get '/discussions/messages/1' - assert_equal 'messages#show', @response.body + get '/discussions/messages/1' + assert_equal 'messages#show', @response.body - get '/messages' - assert_equal 'messages#index', @response.body + get '/messages' + assert_equal 'messages#index', @response.body - get '/messages/1' - assert_equal 'messages#show', @response.body - end + get '/messages/1' + assert_equal 'messages#show', @response.body end def test_router_removes_invalid_conditions - with_test_routes do - get '/tickets' - assert_equal 'tickets#index', @response.body - assert_equal '/tickets', tickets_path - end + get '/tickets' + assert_equal 'tickets#index', @response.body + assert_equal '/tickets', tickets_path end def test_constraints_are_merged_from_scope - with_test_routes do - get '/movies/0001' - assert_equal 'movies#show', @response.body - assert_equal '/movies/0001', movie_path(:id => '0001') - - get '/movies/00001' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') } - - get '/movies/0001/reviews' - assert_equal 'reviews#index', @response.body - assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001') - - get '/movies/00001/reviews' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') } - - get '/movies/0001/reviews/0001' - assert_equal 'reviews#show', @response.body - assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001') - - get '/movies/00001/reviews/0001' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') } - - get '/movies/0001/trailer' - assert_equal 'trailers#show', @response.body - assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001') - - get '/movies/00001/trailer' - assert_equal 'Not Found', @response.body - assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') } - end + get '/movies/0001' + assert_equal 'movies#show', @response.body + assert_equal '/movies/0001', movie_path(:id => '0001') + + get '/movies/00001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:id => '00001') } + + get '/movies/0001/reviews' + assert_equal 'reviews#index', @response.body + assert_equal '/movies/0001/reviews', movie_reviews_path(:movie_id => '0001') + + get '/movies/00001/reviews' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_reviews_path(:movie_id => '00001') } + + get '/movies/0001/reviews/0001' + assert_equal 'reviews#show', @response.body + assert_equal '/movies/0001/reviews/0001', movie_review_path(:movie_id => '0001', :id => '0001') + + get '/movies/00001/reviews/0001' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_path(:movie_id => '00001', :id => '00001') } + + get '/movies/0001/trailer' + assert_equal 'trailers#show', @response.body + assert_equal '/movies/0001/trailer', movie_trailer_path(:movie_id => '0001') + + get '/movies/00001/trailer' + assert_equal 'Not Found', @response.body + assert_raises(ActionController::RoutingError){ movie_trailer_path(:movie_id => '00001') } end def test_only_should_be_read_from_scope - with_test_routes do - get '/only/clubs' - assert_equal 'only/clubs#index', @response.body - assert_equal '/only/clubs', only_clubs_path - - get '/only/clubs/1/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_path(:id => '1') } - - get '/only/clubs/1/players' - assert_equal 'only/players#index', @response.body - assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1') - - get '/only/clubs/1/players/2/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') } - - get '/only/clubs/1/chairman' - assert_equal 'only/chairmen#show', @response.body - assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1') - - get '/only/clubs/1/chairman/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') } - end + get '/only/clubs' + assert_equal 'only/clubs#index', @response.body + assert_equal '/only/clubs', only_clubs_path + + get '/only/clubs/1/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_path(:id => '1') } + + get '/only/clubs/1/players' + assert_equal 'only/players#index', @response.body + assert_equal '/only/clubs/1/players', only_club_players_path(:club_id => '1') + + get '/only/clubs/1/players/2/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_player_path(:club_id => '1', :id => '2') } + + get '/only/clubs/1/chairman' + assert_equal 'only/chairmen#show', @response.body + assert_equal '/only/clubs/1/chairman', only_club_chairman_path(:club_id => '1') + + get '/only/clubs/1/chairman/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_only_club_chairman_path(:club_id => '1') } end def test_except_should_be_read_from_scope - with_test_routes do - get '/except/clubs' - assert_equal 'except/clubs#index', @response.body - assert_equal '/except/clubs', except_clubs_path - - get '/except/clubs/1/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_path(:id => '1') } - - get '/except/clubs/1/players' - assert_equal 'except/players#index', @response.body - assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1') - - get '/except/clubs/1/players/2/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') } - - get '/except/clubs/1/chairman' - assert_equal 'except/chairmen#show', @response.body - assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1') - - get '/except/clubs/1/chairman/edit' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') } - end + get '/except/clubs' + assert_equal 'except/clubs#index', @response.body + assert_equal '/except/clubs', except_clubs_path + + get '/except/clubs/1/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_path(:id => '1') } + + get '/except/clubs/1/players' + assert_equal 'except/players#index', @response.body + assert_equal '/except/clubs/1/players', except_club_players_path(:club_id => '1') + + get '/except/clubs/1/players/2/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_player_path(:club_id => '1', :id => '2') } + + get '/except/clubs/1/chairman' + assert_equal 'except/chairmen#show', @response.body + assert_equal '/except/clubs/1/chairman', except_club_chairman_path(:club_id => '1') + + get '/except/clubs/1/chairman/edit' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { edit_except_club_chairman_path(:club_id => '1') } end def test_only_option_should_override_scope - with_test_routes do - get '/only/sectors' - assert_equal 'only/sectors#index', @response.body - assert_equal '/only/sectors', only_sectors_path - - get '/only/sectors/1' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_path(:id => '1') } - end + get '/only/sectors' + assert_equal 'only/sectors#index', @response.body + assert_equal '/only/sectors', only_sectors_path + + get '/only/sectors/1' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_path(:id => '1') } end def test_only_option_should_not_inherit - with_test_routes do - get '/only/sectors/1/companies/2' - assert_equal 'only/companies#show', @response.body - assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2') - - get '/only/sectors/1/leader' - assert_equal 'only/leaders#show', @response.body - assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1') - end + get '/only/sectors/1/companies/2' + assert_equal 'only/companies#show', @response.body + assert_equal '/only/sectors/1/companies/2', only_sector_company_path(:sector_id => '1', :id => '2') + + get '/only/sectors/1/leader' + assert_equal 'only/leaders#show', @response.body + assert_equal '/only/sectors/1/leader', only_sector_leader_path(:sector_id => '1') end def test_except_option_should_override_scope - with_test_routes do - get '/except/sectors' - assert_equal 'except/sectors#index', @response.body - assert_equal '/except/sectors', except_sectors_path - - get '/except/sectors/1' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_path(:id => '1') } - end + get '/except/sectors' + assert_equal 'except/sectors#index', @response.body + assert_equal '/except/sectors', except_sectors_path + + get '/except/sectors/1' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_path(:id => '1') } end def test_except_option_should_not_inherit - with_test_routes do - get '/except/sectors/1/companies/2' - assert_equal 'except/companies#show', @response.body - assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2') - - get '/except/sectors/1/leader' - assert_equal 'except/leaders#show', @response.body - assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1') - end + get '/except/sectors/1/companies/2' + assert_equal 'except/companies#show', @response.body + assert_equal '/except/sectors/1/companies/2', except_sector_company_path(:sector_id => '1', :id => '2') + + get '/except/sectors/1/leader' + assert_equal 'except/leaders#show', @response.body + assert_equal '/except/sectors/1/leader', except_sector_leader_path(:sector_id => '1') end def test_except_option_should_override_scoped_only - with_test_routes do - get '/only/sectors/1/managers' - assert_equal 'only/managers#index', @response.body - assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1') - - get '/only/sectors/1/managers/2' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') } - end + get '/only/sectors/1/managers' + assert_equal 'only/managers#index', @response.body + assert_equal '/only/sectors/1/managers', only_sector_managers_path(:sector_id => '1') + + get '/only/sectors/1/managers/2' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_manager_path(:sector_id => '1', :id => '2') } end def test_only_option_should_override_scoped_except - with_test_routes do - get '/except/sectors/1/managers' - assert_equal 'except/managers#index', @response.body - assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1') - - get '/except/sectors/1/managers/2' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') } - end + get '/except/sectors/1/managers' + assert_equal 'except/managers#index', @response.body + assert_equal '/except/sectors/1/managers', except_sector_managers_path(:sector_id => '1') + + get '/except/sectors/1/managers/2' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_manager_path(:sector_id => '1', :id => '2') } end def test_only_scope_should_override_parent_scope - with_test_routes do - get '/only/sectors/1/companies/2/divisions' - assert_equal 'only/divisions#index', @response.body - assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2') - - get '/only/sectors/1/companies/2/divisions/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } - end + get '/only/sectors/1/companies/2/divisions' + assert_equal 'only/divisions#index', @response.body + assert_equal '/only/sectors/1/companies/2/divisions', only_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + + get '/only/sectors/1/companies/2/divisions/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } end def test_except_scope_should_override_parent_scope - with_test_routes do - get '/except/sectors/1/companies/2/divisions' - assert_equal 'except/divisions#index', @response.body - assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2') - - get '/except/sectors/1/companies/2/divisions/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } - end + get '/except/sectors/1/companies/2/divisions' + assert_equal 'except/divisions#index', @response.body + assert_equal '/except/sectors/1/companies/2/divisions', except_sector_company_divisions_path(:sector_id => '1', :company_id => '2') + + get '/except/sectors/1/companies/2/divisions/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_company_division_path(:sector_id => '1', :company_id => '2', :id => '3') } end def test_except_scope_should_override_parent_only_scope - with_test_routes do - get '/only/sectors/1/companies/2/departments' - assert_equal 'only/departments#index', @response.body - assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2') - - get '/only/sectors/1/companies/2/departments/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } - end + get '/only/sectors/1/companies/2/departments' + assert_equal 'only/departments#index', @response.body + assert_equal '/only/sectors/1/companies/2/departments', only_sector_company_departments_path(:sector_id => '1', :company_id => '2') + + get '/only/sectors/1/companies/2/departments/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { only_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } end def test_only_scope_should_override_parent_except_scope - with_test_routes do - get '/except/sectors/1/companies/2/departments' - assert_equal 'except/departments#index', @response.body - assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2') - - get '/except/sectors/1/companies/2/departments/3' - assert_equal 'Not Found', @response.body - assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } - end + get '/except/sectors/1/companies/2/departments' + assert_equal 'except/departments#index', @response.body + assert_equal '/except/sectors/1/companies/2/departments', except_sector_company_departments_path(:sector_id => '1', :company_id => '2') + + get '/except/sectors/1/companies/2/departments/3' + assert_equal 'Not Found', @response.body + assert_raise(NoMethodError) { except_sector_company_department_path(:sector_id => '1', :company_id => '2', :id => '3') } end def test_resources_are_not_pluralized - with_test_routes do - get '/transport/taxis' - assert_equal 'transport/taxis#index', @response.body - assert_equal '/transport/taxis', transport_taxis_path + get '/transport/taxis' + assert_equal 'transport/taxis#index', @response.body + assert_equal '/transport/taxis', transport_taxis_path - get '/transport/taxis/new' - assert_equal 'transport/taxis#new', @response.body - assert_equal '/transport/taxis/new', new_transport_taxi_path + get '/transport/taxis/new' + assert_equal 'transport/taxis#new', @response.body + assert_equal '/transport/taxis/new', new_transport_taxi_path - post '/transport/taxis' - assert_equal 'transport/taxis#create', @response.body + post '/transport/taxis' + assert_equal 'transport/taxis#create', @response.body - get '/transport/taxis/1' - assert_equal 'transport/taxis#show', @response.body - assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1') + get '/transport/taxis/1' + assert_equal 'transport/taxis#show', @response.body + assert_equal '/transport/taxis/1', transport_taxi_path(:id => '1') - get '/transport/taxis/1/edit' - assert_equal 'transport/taxis#edit', @response.body - assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1') + get '/transport/taxis/1/edit' + assert_equal 'transport/taxis#edit', @response.body + assert_equal '/transport/taxis/1/edit', edit_transport_taxi_path(:id => '1') - put '/transport/taxis/1' - assert_equal 'transport/taxis#update', @response.body + put '/transport/taxis/1' + assert_equal 'transport/taxis#update', @response.body - delete '/transport/taxis/1' - assert_equal 'transport/taxis#destroy', @response.body - end + delete '/transport/taxis/1' + assert_equal 'transport/taxis#destroy', @response.body end def test_singleton_resources_are_not_singularized - with_test_routes do - get '/medical/taxis/new' - assert_equal 'medical/taxes#new', @response.body - assert_equal '/medical/taxis/new', new_medical_taxis_path + get '/medical/taxis/new' + assert_equal 'medical/taxis#new', @response.body + assert_equal '/medical/taxis/new', new_medical_taxis_path - post '/medical/taxis' - assert_equal 'medical/taxes#create', @response.body + post '/medical/taxis' + assert_equal 'medical/taxis#create', @response.body - get '/medical/taxis' - assert_equal 'medical/taxes#show', @response.body - assert_equal '/medical/taxis', medical_taxis_path + get '/medical/taxis' + assert_equal 'medical/taxis#show', @response.body + assert_equal '/medical/taxis', medical_taxis_path - get '/medical/taxis/edit' - assert_equal 'medical/taxes#edit', @response.body - assert_equal '/medical/taxis/edit', edit_medical_taxis_path + get '/medical/taxis/edit' + assert_equal 'medical/taxis#edit', @response.body + assert_equal '/medical/taxis/edit', edit_medical_taxis_path - put '/medical/taxis' - assert_equal 'medical/taxes#update', @response.body + put '/medical/taxis' + assert_equal 'medical/taxis#update', @response.body - delete '/medical/taxis' - assert_equal 'medical/taxes#destroy', @response.body - end + delete '/medical/taxis' + assert_equal 'medical/taxis#destroy', @response.body end def test_greedy_resource_id_regexp_doesnt_match_edit_and_custom_action - with_test_routes do - get '/sections/1/edit' - assert_equal 'sections#edit', @response.body - assert_equal '/sections/1/edit', edit_section_path(:id => '1') - - get '/sections/1/preview' - assert_equal 'sections#preview', @response.body - assert_equal '/sections/1/preview', preview_section_path(:id => '1') - end + get '/sections/1/edit' + assert_equal 'sections#edit', @response.body + assert_equal '/sections/1/edit', edit_section_path(:id => '1') + + get '/sections/1/preview' + assert_equal 'sections#preview', @response.body + assert_equal '/sections/1/preview', preview_section_path(:id => '1') end def test_resource_constraints_are_pushed_to_scope - with_test_routes do - get '/wiki/articles/Ruby_on_Rails_3.0' - assert_equal 'wiki/articles#show', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0') - - get '/wiki/articles/Ruby_on_Rails_3.0/comments/new' - assert_equal 'wiki/comments#new', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0') - - post '/wiki/articles/Ruby_on_Rails_3.0/comments' - assert_equal 'wiki/comments#create', @response.body - assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0') - end + get '/wiki/articles/Ruby_on_Rails_3.0' + assert_equal 'wiki/articles#show', @response.body + assert_equal '/wiki/articles/Ruby_on_Rails_3.0', wiki_article_path(:id => 'Ruby_on_Rails_3.0') + + get '/wiki/articles/Ruby_on_Rails_3.0/comments/new' + assert_equal 'wiki/comments#new', @response.body + assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments/new', new_wiki_article_comment_path(:article_id => 'Ruby_on_Rails_3.0') + + post '/wiki/articles/Ruby_on_Rails_3.0/comments' + assert_equal 'wiki/comments#create', @response.body + assert_equal '/wiki/articles/Ruby_on_Rails_3.0/comments', wiki_article_comments_path(:article_id => 'Ruby_on_Rails_3.0') end def test_resources_path_can_be_a_symbol - with_test_routes do - get '/pages' - assert_equal 'wiki_pages#index', @response.body - assert_equal '/pages', wiki_pages_path - - get '/pages/Ruby_on_Rails' - assert_equal 'wiki_pages#show', @response.body - assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails') - - get '/my_account' - assert_equal 'wiki_accounts#show', @response.body - assert_equal '/my_account', wiki_account_path - end + get '/pages' + assert_equal 'wiki_pages#index', @response.body + assert_equal '/pages', wiki_pages_path + + get '/pages/Ruby_on_Rails' + assert_equal 'wiki_pages#show', @response.body + assert_equal '/pages/Ruby_on_Rails', wiki_page_path(:id => 'Ruby_on_Rails') + + get '/my_account' + assert_equal 'wiki_accounts#show', @response.body + assert_equal '/my_account', wiki_account_path end def test_redirect_https - with_test_routes do - with_https do - get '/secure' - verify_redirect 'https://www.example.com/secure/login' - end + with_https do + get '/secure' + verify_redirect 'https://www.example.com/secure/login' end end @@ -2303,7 +2100,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest def test_named_routes_collision_is_avoided_unless_explicitly_given_as assert_equal "/c/1", routes_collision_path(1) - assert_equal "/fc", routes_forced_collision_path + assert_equal "/fc/1", routes_forced_collision_path(1) end def test_redirect_argument_error @@ -2391,11 +2188,20 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest assert_equal "/posts/1/admin", post_admin_root_path(:post_id => '1') end -private - def with_test_routes - yield + def test_custom_param + get '/profiles/bob' + assert_equal 'profiles#show', @response.body + assert_equal 'bob', @request.params[:username] + + get '/profiles/bob/details' + assert_equal 'bob', @request.params[:username] + + get '/profiles/bob/messages/34' + assert_equal 'bob', @request.params[:profile_username] + assert_equal '34', @request.params[:id] end +private def with_https old_https = https? https! @@ -2450,6 +2256,32 @@ class TestAppendingRoutes < ActionDispatch::IntegrationTest end end +class TestNamespaceWithControllerOption < ActionDispatch::IntegrationTest + module ::Admin + class StorageFilesController < ActionController::Base + def index + render :text => "admin/storage_files#index" + end + end + end + + DefaultScopeRoutes = ActionDispatch::Routing::RouteSet.new + DefaultScopeRoutes.draw do + namespace :admin do + resources :storage_files, :controller => "StorageFiles" + end + end + + def app + DefaultScopeRoutes + end + + def test_controller_options + get '/admin/storage_files' + assert_equal "admin/storage_files#index", @response.body + end +end + class TestDefaultScope < ActionDispatch::IntegrationTest module ::Blog class PostsController < ActionController::Base @@ -2563,3 +2395,60 @@ class TestUnicodePaths < ActionDispatch::IntegrationTest assert_equal "200", @response.code end end + +class TestMultipleNestedController < ActionDispatch::IntegrationTest + module ::Foo + module Bar + class BazController < ActionController::Base + def index + render :inline => "<%= url_for :controller => '/pooh', :action => 'index' %>" + end + end + end + end + + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + namespace :foo do + namespace :bar do + match "baz" => "baz#index" + end + end + match "pooh" => "pooh#index" + end + end + + include Routes.url_helpers + def app; Routes end + + test "controller option which starts with '/' from multiple nested controller" do + get "/foo/bar/baz" + assert_equal "/pooh", @response.body + end + +end + +class TestTildeAndMinusPaths < ActionDispatch::IntegrationTest + Routes = ActionDispatch::Routing::RouteSet.new.tap do |app| + app.draw do + ok = lambda { |env| [200, { 'Content-Type' => 'text/plain' }, []] } + + match "/~user" => ok + match "/young-and-fine" => ok + end + end + + include Routes.url_helpers + def app; Routes end + + test 'recognizes tilde path' do + get "/~user" + assert_equal "200", @response.code + end + + test 'recognizes minus path' do + get "/young-and-fine" + assert_equal "200", @response.code + end + +end diff --git a/actionpack/test/dispatch/ssl_test.rb b/actionpack/test/dispatch/ssl_test.rb new file mode 100644 index 0000000000..6f075a9074 --- /dev/null +++ b/actionpack/test/dispatch/ssl_test.rb @@ -0,0 +1,157 @@ +require 'abstract_unit' + +class SSLTest < ActionDispatch::IntegrationTest + def default_app + lambda { |env| + headers = {'Content-Type' => "text/html"} + headers['Set-Cookie'] = "id=1; path=/\ntoken=abc; path=/; secure; HttpOnly" + [200, headers, ["OK"]] + } + end + + def app + @app ||= ActionDispatch::SSL.new(default_app) + end + attr_writer :app + + def test_allows_https_url + get "https://example.org/path?key=value" + assert_response :success + end + + def test_allows_https_proxy_header_url + get "http://example.org/", {}, 'HTTP_X_FORWARDED_PROTO' => "https" + assert_response :success + end + + def test_redirects_http_to_https + get "http://example.org/path?key=value" + assert_response :redirect + assert_equal "https://example.org/path?key=value", + response.headers['Location'] + end + + def test_hsts_header_by_default + get "https://example.org/" + assert_equal "max-age=31536000", + response.headers['Strict-Transport-Security'] + end + + def test_hsts_header + self.app = ActionDispatch::SSL.new(default_app, :hsts => true) + get "https://example.org/" + assert_equal "max-age=31536000", + response.headers['Strict-Transport-Security'] + end + + def test_disable_hsts_header + self.app = ActionDispatch::SSL.new(default_app, :hsts => false) + get "https://example.org/" + refute response.headers['Strict-Transport-Security'] + end + + def test_hsts_expires + self.app = ActionDispatch::SSL.new(default_app, :hsts => { :expires => 500 }) + get "https://example.org/" + assert_equal "max-age=500", + 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/" + assert_equal "max-age=31536000; includeSubDomains", + response.headers['Strict-Transport-Security'] + end + + def test_flag_cookies_as_secure + get "https://example.org/" + assert_equal ["id=1; path=/; secure", "token=abc; path=/; secure; HttpOnly" ], + response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure_at_end_of_line + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/; HttpOnly; secure"], + response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure_with_more_spaces_before + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/; HttpOnly; secure" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/; HttpOnly; secure"], + response.headers['Set-Cookie'].split("\n") + end + + def test_flag_cookies_as_secure_with_more_spaces_after + self.app = ActionDispatch::SSL.new(lambda { |env| + headers = { + 'Content-Type' => "text/html", + 'Set-Cookie' => "problem=def; path=/; secure; HttpOnly" + } + [200, headers, ["OK"]] + }) + + get "https://example.org/" + assert_equal ["problem=def; path=/; secure; HttpOnly"], + response.headers['Set-Cookie'].split("\n") + end + + def test_no_cookies + self.app = ActionDispatch::SSL.new(lambda { |env| + [200, {'Content-Type' => "text/html"}, ["OK"]] + }) + get "https://example.org/" + assert !response.headers['Set-Cookie'] + end + + def test_redirect_to_host + self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") + get "http://example.org/path?key=value" + assert_equal "https://ssl.example.org/path?key=value", + response.headers['Location'] + end + + def test_redirect_to_port + self.app = ActionDispatch::SSL.new(default_app, :port => 8443) + get "http://example.org/path?key=value" + assert_equal "https://example.org:8443/path?key=value", + response.headers['Location'] + end + + def test_redirect_to_host_and_port + self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org", :port => 8443) + get "http://example.org/path?key=value" + assert_equal "https://ssl.example.org:8443/path?key=value", + response.headers['Location'] + end + + def test_redirect_to_secure_host_when_on_subdomain + self.app = ActionDispatch::SSL.new(default_app, :host => "ssl.example.org") + get "http://ssl.example.org/path?key=value" + assert_equal "https://ssl.example.org/path?key=value", + response.headers['Location'] + end + + def test_redirect_to_secure_subdomain_when_on_deep_subdomain + self.app = ActionDispatch::SSL.new(default_app, :host => "example.co.uk") + get "http://double.rainbow.what.does.it.mean.example.co.uk/path?key=value" + assert_equal "https://example.co.uk/path?key=value", + response.headers['Location'] + end +end diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 9f3cbd19ef..112f470786 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -1,10 +1,16 @@ +# encoding: utf-8 require 'abstract_unit' +require 'rbconfig' module StaticTests def test_serves_dynamic_content assert_equal "Hello, World!", get("/nofile").body end + def test_handles_urls_with_bad_encoding + assert_equal "Hello, World!", get("/doorkeeper%E3E4").body + end + def test_sets_cache_control response = get("/index.html") assert_html "/index.html", response @@ -30,6 +36,91 @@ module StaticTests assert_html "/foo/index.html", get("/foo") end + def test_served_static_file_with_non_english_filename + assert_html "means hello in Japanese\n", get("/foo/#{Rack::Utils.escape("こんにちは.html")}") + end + + + def test_serves_static_file_with_exclamation_mark_in_filename + with_static_file "/foo/foo!bar.html" do |file| + assert_html file, get("/foo/foo%21bar.html") + assert_html file, get("/foo/foo!bar.html") + end + end + + def test_serves_static_file_with_dollar_sign_in_filename + with_static_file "/foo/foo$bar.html" do |file| + assert_html file, get("/foo/foo%24bar.html") + assert_html file, get("/foo/foo$bar.html") + end + end + + def test_serves_static_file_with_ampersand_in_filename + with_static_file "/foo/foo&bar.html" do |file| + assert_html file, get("/foo/foo%26bar.html") + assert_html file, get("/foo/foo&bar.html") + end + end + + def test_serves_static_file_with_apostrophe_in_filename + with_static_file "/foo/foo'bar.html" do |file| + assert_html file, get("/foo/foo%27bar.html") + assert_html file, get("/foo/foo'bar.html") + end + end + + def test_serves_static_file_with_parentheses_in_filename + with_static_file "/foo/foo(bar).html" do |file| + assert_html file, get("/foo/foo%28bar%29.html") + assert_html file, get("/foo/foo(bar).html") + end + end + + def test_serves_static_file_with_plus_sign_in_filename + with_static_file "/foo/foo+bar.html" do |file| + assert_html file, get("/foo/foo%2Bbar.html") + assert_html file, get("/foo/foo+bar.html") + end + end + + def test_serves_static_file_with_comma_in_filename + with_static_file "/foo/foo,bar.html" do |file| + assert_html file, get("/foo/foo%2Cbar.html") + assert_html file, get("/foo/foo,bar.html") + end + end + + def test_serves_static_file_with_semi_colon_in_filename + with_static_file "/foo/foo;bar.html" do |file| + assert_html file, get("/foo/foo%3Bbar.html") + assert_html file, get("/foo/foo;bar.html") + end + end + + def test_serves_static_file_with_at_symbol_in_filename + with_static_file "/foo/foo@bar.html" do |file| + assert_html file, get("/foo/foo%40bar.html") + assert_html file, get("/foo/foo@bar.html") + end + end + + # Windows doesn't allow \ / : * ? " < > | in filenames + unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ + def test_serves_static_file_with_colon + with_static_file "/foo/foo:bar.html" do |file| + assert_html file, get("/foo/foo%3Abar.html") + assert_html file, get("/foo/foo:bar.html") + end + end + + def test_serves_static_file_with_asterisk + with_static_file "/foo/foo*bar.html" do |file| + assert_html file, get("/foo/foo%2Abar.html") + assert_html file, get("/foo/foo*bar.html") + end + end + end + private def assert_html(body, response) @@ -40,6 +131,14 @@ module StaticTests def get(path) Rack::MockRequest.new(@app).request("GET", path) end + + def with_static_file(file) + path = "#{FIXTURE_LOAD_PATH}/public" + file + File.open(path, "wb+") { |f| f.write(file) } + yield file + ensure + File.delete(path) + end end class StaticTest < ActiveSupport::TestCase @@ -53,4 +152,4 @@ class StaticTest < ActiveSupport::TestCase end include StaticTests -end
\ No newline at end of file +end diff --git a/actionpack/test/dispatch/uploaded_file_test.rb b/actionpack/test/dispatch/uploaded_file_test.rb index 0b95291e18..e69c1fbed4 100644 --- a/actionpack/test/dispatch/uploaded_file_test.rb +++ b/actionpack/test/dispatch/uploaded_file_test.rb @@ -12,7 +12,7 @@ module ActionDispatch uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) assert_equal 'foo', uf.original_filename end - + def test_filename_should_be_in_utf_8 uf = Http::UploadedFile.new(:filename => 'foo', :tempfile => Object.new) assert_equal "UTF-8", uf.original_filename.encoding.to_s @@ -65,6 +65,12 @@ module ActionDispatch end end + def test_delegate_eof_to_tempfile + tf = Class.new { def eof?; true end; } + uf = Http::UploadedFile.new(:tempfile => tf.new) + assert uf.eof? + end + def test_respond_to? tf = Class.new { def read; yield end } uf = Http::UploadedFile.new(:tempfile => tf.new) diff --git a/actionpack/test/fixtures/addresses/list.erb b/actionpack/test/fixtures/addresses/list.erb deleted file mode 100644 index c75e01eece..0000000000 --- a/actionpack/test/fixtures/addresses/list.erb +++ /dev/null @@ -1 +0,0 @@ -We only need to get this far! diff --git a/actionpack/test/fixtures/layouts/with_html_partial.html.erb b/actionpack/test/fixtures/layouts/with_html_partial.html.erb new file mode 100644 index 0000000000..fd2896aeaa --- /dev/null +++ b/actionpack/test/fixtures/layouts/with_html_partial.html.erb @@ -0,0 +1 @@ +<%= render :partial => "partial_only_html" %><%= yield %> diff --git a/actionpack/test/fixtures/public/foo/こんにちは.html b/actionpack/test/fixtures/public/foo/こんにちは.html new file mode 100644 index 0000000000..1df9166522 --- /dev/null +++ b/actionpack/test/fixtures/public/foo/こんにちは.html @@ -0,0 +1 @@ +means hello in Japanese diff --git a/actionpack/test/fixtures/reply.rb b/actionpack/test/fixtures/reply.rb index 19cba93673..0d3b0a7c98 100644 --- a/actionpack/test/fixtures/reply.rb +++ b/actionpack/test/fixtures/reply.rb @@ -1,5 +1,5 @@ class Reply < ActiveRecord::Base - scope :base + scope :base, -> { scoped } belongs_to :topic, :include => [:replies] belongs_to :developer diff --git a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css b/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css deleted file mode 100644 index bfb90bfa48..0000000000 --- a/actionpack/test/fixtures/sprockets/alternate/stylesheets/style.css +++ /dev/null @@ -1 +0,0 @@ -/* Different from other style.css */
\ No newline at end of file diff --git a/actionpack/test/fixtures/sprockets/app/fonts/dir/font.ttf b/actionpack/test/fixtures/sprockets/app/fonts/dir/font.ttf deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/fonts/dir/font.ttf +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/fonts/font.ttf b/actionpack/test/fixtures/sprockets/app/fonts/font.ttf deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/fonts/font.ttf +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/images/logo.png b/actionpack/test/fixtures/sprockets/app/images/logo.png Binary files differdeleted file mode 100644 index d5edc04e65..0000000000 --- a/actionpack/test/fixtures/sprockets/app/images/logo.png +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/application.js b/actionpack/test/fixtures/sprockets/app/javascripts/application.js deleted file mode 100644 index e611d2b129..0000000000 --- a/actionpack/test/fixtures/sprockets/app/javascripts/application.js +++ /dev/null @@ -1 +0,0 @@ -//= require xmlhr diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/javascripts/dir/xmlhr.js +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js b/actionpack/test/fixtures/sprockets/app/javascripts/extra.js deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/javascripts/extra.js +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js b/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/javascripts/xmlhr.js +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css b/actionpack/test/fixtures/sprockets/app/stylesheets/application.css deleted file mode 100644 index 2365eaa4cd..0000000000 --- a/actionpack/test/fixtures/sprockets/app/stylesheets/application.css +++ /dev/null @@ -1 +0,0 @@ -/*= require style */ diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/stylesheets/dir/style.css +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css b/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/stylesheets/extra.css +++ /dev/null diff --git a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css b/actionpack/test/fixtures/sprockets/app/stylesheets/style.css deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/fixtures/sprockets/app/stylesheets/style.css +++ /dev/null diff --git a/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb b/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb new file mode 100644 index 0000000000..e918ba8f83 --- /dev/null +++ b/actionpack/test/fixtures/test/_b_layout_for_partial.html.erb @@ -0,0 +1 @@ +<b><%= yield %></b>
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/_partial_html_erb.html.erb b/actionpack/test/fixtures/test/_partial_html_erb.html.erb new file mode 100644 index 0000000000..4b54875782 --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_html_erb.html.erb @@ -0,0 +1 @@ +<%= "partial.html.erb" %> diff --git a/actionpack/test/fixtures/test/_partial_only_html.html b/actionpack/test/fixtures/test/_partial_only_html.html new file mode 100644 index 0000000000..d2d630bd40 --- /dev/null +++ b/actionpack/test/fixtures/test/_partial_only_html.html @@ -0,0 +1 @@ +only html partial
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/one.html.erb b/actionpack/test/fixtures/test/one.html.erb new file mode 100644 index 0000000000..0151874809 --- /dev/null +++ b/actionpack/test/fixtures/test/one.html.erb @@ -0,0 +1 @@ +<%= render :partial => "test/two" %> world
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/with_html_partial.html.erb b/actionpack/test/fixtures/test/with_html_partial.html.erb new file mode 100644 index 0000000000..d84d909d64 --- /dev/null +++ b/actionpack/test/fixtures/test/with_html_partial.html.erb @@ -0,0 +1 @@ +<strong><%= render :partial => "partial_only_html" %></strong> diff --git a/actionpack/test/fixtures/test/with_partial.html.erb b/actionpack/test/fixtures/test/with_partial.html.erb new file mode 100644 index 0000000000..7502364cf5 --- /dev/null +++ b/actionpack/test/fixtures/test/with_partial.html.erb @@ -0,0 +1 @@ +<strong><%= render :partial => "partial_only" %></strong> diff --git a/actionpack/test/fixtures/test/with_partial.text.erb b/actionpack/test/fixtures/test/with_partial.text.erb new file mode 100644 index 0000000000..5f068ebf27 --- /dev/null +++ b/actionpack/test/fixtures/test/with_partial.text.erb @@ -0,0 +1 @@ +**<%= render :partial => "partial_only" %>** diff --git a/actionpack/test/fixtures/test/with_xml_template.html.erb b/actionpack/test/fixtures/test/with_xml_template.html.erb new file mode 100644 index 0000000000..e54a7cd001 --- /dev/null +++ b/actionpack/test/fixtures/test/with_xml_template.html.erb @@ -0,0 +1 @@ +<%= render :template => "test/greeting", :formats => :xml %> diff --git a/actionpack/test/fixtures/with_format.json.erb b/actionpack/test/fixtures/with_format.json.erb new file mode 100644 index 0000000000..a7f480ab1d --- /dev/null +++ b/actionpack/test/fixtures/with_format.json.erb @@ -0,0 +1 @@ +<%= render :partial => 'missing', :formats => [:json] %> diff --git a/actionpack/test/lib/controller/fake_controllers.rb b/actionpack/test/lib/controller/fake_controllers.rb index 09692f77b5..1a2863b689 100644 --- a/actionpack/test/lib/controller/fake_controllers.rb +++ b/actionpack/test/lib/controller/fake_controllers.rb @@ -1,11 +1,7 @@ -class << Object; alias_method :const_available?, :const_defined?; end - class ContentController < ActionController::Base; end module Admin - class << self; alias_method :const_available?, :const_defined?; end class AccountsController < ActionController::Base; end - class NewsFeedController < ActionController::Base; end class PostsController < ActionController::Base; end class StuffController < ActionController::Base; end class UserController < ActionController::Base; end @@ -17,46 +13,23 @@ module Api class ProductsController < ActionController::Base; end end -# TODO: Reduce the number of test controllers we use class AccountController < ActionController::Base; end -class AddressesController < ActionController::Base; end class ArchiveController < ActionController::Base; end class ArticlesController < ActionController::Base; end class BarController < ActionController::Base; end class BlogController < ActionController::Base; end class BooksController < ActionController::Base; end -class BraveController < ActionController::Base; end class CarsController < ActionController::Base; end class CcController < ActionController::Base; end class CController < ActionController::Base; end -class ElsewhereController < ActionController::Base; end class FooController < ActionController::Base; end class GeocodeController < ActionController::Base; end -class HiController < ActionController::Base; end -class ImageController < ActionController::Base; end class NewsController < ActionController::Base; end class NotesController < ActionController::Base; end +class PagesController < ActionController::Base; end class PeopleController < ActionController::Base; end class PostsController < ActionController::Base; end -class SessionsController < ActionController::Base; end -class StuffController < ActionController::Base; end class SubpathBooksController < ActionController::Base; end class SymbolsController < ActionController::Base; end class UserController < ActionController::Base; end -class WeblogController < ActionController::Base; end - -# For speed test -class SpeedController < ActionController::Base; end -class SearchController < SpeedController; end -class VideosController < SpeedController; end -class VideoFileController < SpeedController; end -class VideoSharesController < SpeedController; end -class VideoAbusesController < SpeedController; end -class VideoUploadsController < SpeedController; end -class VideoVisitsController < SpeedController; end -class UsersController < SpeedController; end -class SettingsController < SpeedController; end -class ChannelsController < SpeedController; end -class ChannelVideosController < SpeedController; end -class LostPasswordsController < SpeedController; end -class PagesController < SpeedController; end +class UsersController < ActionController::Base; end diff --git a/actionpack/test/lib/controller/fake_models.rb b/actionpack/test/lib/controller/fake_models.rb index f2362714d7..bbb4cc5ef3 100644 --- a/actionpack/test/lib/controller/fake_models.rb +++ b/actionpack/test/lib/controller/fake_models.rb @@ -34,6 +34,16 @@ end class GoodCustomer < Customer end +class ValidatedCustomer < Customer + def errors + if name =~ /Sikachu/i + [] + else + [{:name => "is invalid"}] + end + end +end + module Quiz class Question < Struct.new(:name, :id) extend ActiveModel::Naming diff --git a/actionpack/test/routing/helper_test.rb b/actionpack/test/routing/helper_test.rb new file mode 100644 index 0000000000..a5588d95fa --- /dev/null +++ b/actionpack/test/routing/helper_test.rb @@ -0,0 +1,31 @@ +require 'abstract_unit' + +module ActionDispatch + module Routing + class HelperTest < ActiveSupport::TestCase + class Duck + def to_param + nil + end + end + + def test_exception + rs = ::ActionDispatch::Routing::RouteSet.new + rs.draw do + resources :ducks do + member do + get :pond + end + end + end + + x = Class.new { + include rs.url_helpers + } + assert_raises ActionController::RoutingError do + x.new.pond_duck_path Duck.new + end + end + end + end +end diff --git a/actionpack/test/template/active_model_helper_test.rb b/actionpack/test/template/active_model_helper_test.rb index 52be0f1762..24511df444 100644 --- a/actionpack/test/template/active_model_helper_test.rb +++ b/actionpack/test/template/active_model_helper_test.rb @@ -29,28 +29,28 @@ class ActiveModelHelperTest < ActionView::TestCase def test_text_area_with_errors assert_dom_equal( - %(<div class="field_with_errors"><textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea></div>), + %(<div class="field_with_errors"><textarea id="post_body" name="post[body]">\nBack to the hill and over it again!</textarea></div>), text_area("post", "body") ) end def test_text_field_with_errors assert_dom_equal( - %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /></div>), + %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /></div>), text_field("post", "author_name") ) end def test_date_select_with_errors assert_dom_equal( - %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n</div>), + %(<div class="field_with_errors"><select id="post_updated_at_1i" name="post[updated_at(1i)]">\n<option selected="selected" value="2004">2004</option>\n<option value="2005">2005</option>\n</select>\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n</div>), date_select("post", "updated_at", :discard_month => true, :discard_day => true, :start_year => 2004, :end_year => 2005) ) end def test_datetime_select_with_errors assert_dom_equal( - %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="15" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), + %(<div class="field_with_errors"><input id="post_updated_at_1i" name="post[updated_at(1i)]" type="hidden" value="2004" />\n<input id="post_updated_at_2i" name="post[updated_at(2i)]" type="hidden" value="6" />\n<input id="post_updated_at_3i" name="post[updated_at(3i)]" type="hidden" value="1" />\n<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n<option selected="selected" value="00">00</option>\n<option value="01">01</option>\n<option value="02">02</option>\n<option value="03">03</option>\n<option value="04">04</option>\n<option value="05">05</option>\n<option value="06">06</option>\n<option value="07">07</option>\n<option value="08">08</option>\n<option value="09">09</option>\n<option value="10">10</option>\n<option value="11">11</option>\n<option value="12">12</option>\n<option value="13">13</option>\n<option value="14">14</option>\n<option value="15">15</option>\n<option value="16">16</option>\n<option value="17">17</option>\n<option value="18">18</option>\n<option value="19">19</option>\n<option value="20">20</option>\n<option value="21">21</option>\n<option value="22">22</option>\n<option value="23">23</option>\n</select>\n : <select id="post_updated_at_5i" name="post[updated_at(5i)]">\n<option selected="selected" value="00">00</option>\n</select>\n</div>), datetime_select("post", "updated_at", :discard_year => true, :discard_month => true, :discard_day => true, :minute_step => 60) ) end @@ -76,7 +76,7 @@ class ActiveModelHelperTest < ActionView::TestCase end assert_dom_equal( - %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" size="30" type="text" value="" /> <span class="error">can't be empty</span></div>), + %(<div class="field_with_errors"><input id="post_author_name" name="post[author_name]" type="text" value="" /> <span class="error">can't be empty</span></div>), text_field("post", "author_name") ) ensure diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 90c2ca7a3d..58ff055fc2 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -38,6 +38,7 @@ class AssetTagHelperTest < ActionView::TestCase @controller = BasicController.new @request = Class.new do + attr_accessor :script_name def protocol() 'http://' end def ssl?() false end def host_with_port() 'localhost' end @@ -88,6 +89,18 @@ class AssetTagHelperTest < ActionView::TestCase %(path_to_javascript("/super/xmlhr.js")) => %(/super/xmlhr.js) } + JavascriptUrlToTag = { + %(javascript_url("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js), + %(javascript_url("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js), + %(javascript_url("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js) + } + + UrlToJavascriptToTag = { + %(url_to_javascript("xmlhr")) => %(http://www.example.com/javascripts/xmlhr.js), + %(url_to_javascript("super/xmlhr")) => %(http://www.example.com/javascripts/super/xmlhr.js), + %(url_to_javascript("/super/xmlhr.js")) => %(http://www.example.com/super/xmlhr.js) + } + JavascriptIncludeToTag = { %(javascript_include_tag("bank")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>), %(javascript_include_tag("bank.js")) => %(<script src="/javascripts/bank.js" type="text/javascript"></script>), @@ -119,6 +132,20 @@ class AssetTagHelperTest < ActionView::TestCase %(path_to_stylesheet('/dir/file.rcss')) => %(/dir/file.rcss) } + StyleUrlToTag = { + %(stylesheet_url("bank")) => %(http://www.example.com/stylesheets/bank.css), + %(stylesheet_url("bank.css")) => %(http://www.example.com/stylesheets/bank.css), + %(stylesheet_url('subdir/subdir')) => %(http://www.example.com/stylesheets/subdir/subdir.css), + %(stylesheet_url('/subdir/subdir.css')) => %(http://www.example.com/subdir/subdir.css) + } + + UrlToStyleToTag = { + %(url_to_stylesheet("style")) => %(http://www.example.com/stylesheets/style.css), + %(url_to_stylesheet("style.css")) => %(http://www.example.com/stylesheets/style.css), + %(url_to_stylesheet('dir/file')) => %(http://www.example.com/stylesheets/dir/file.css), + %(url_to_stylesheet('/dir/file.rcss')) => %(http://www.example.com/dir/file.rcss) + } + StyleLinkToTag = { %(stylesheet_link_tag("bank")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />), %(stylesheet_link_tag("bank.css")) => %(<link href="/stylesheets/bank.css" media="screen" rel="stylesheet" type="text/css" />), @@ -149,6 +176,20 @@ class AssetTagHelperTest < ActionView::TestCase %(path_to_image("/dir/xml.png")) => %(/dir/xml.png) } + ImageUrlToTag = { + %(image_url("xml")) => %(http://www.example.com/images/xml), + %(image_url("xml.png")) => %(http://www.example.com/images/xml.png), + %(image_url("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png), + %(image_url("/dir/xml.png")) => %(http://www.example.com/dir/xml.png) + } + + UrlToImageToTag = { + %(url_to_image("xml")) => %(http://www.example.com/images/xml), + %(url_to_image("xml.png")) => %(http://www.example.com/images/xml.png), + %(url_to_image("dir/xml.png")) => %(http://www.example.com/images/dir/xml.png), + %(url_to_image("/dir/xml.png")) => %(http://www.example.com/dir/xml.png) + } + ImageLinkToTag = { %(image_tag("xml.png")) => %(<img alt="Xml" src="/images/xml.png" />), %(image_tag("rss.gif", :alt => "rss syndication")) => %(<img alt="rss syndication" src="/images/rss.gif" />), @@ -189,24 +230,38 @@ class AssetTagHelperTest < ActionView::TestCase %(path_to_video("/dir/xml.ogg")) => %(/dir/xml.ogg) } + VideoUrlToTag = { + %(video_url("xml")) => %(http://www.example.com/videos/xml), + %(video_url("xml.ogg")) => %(http://www.example.com/videos/xml.ogg), + %(video_url("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg), + %(video_url("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg) + } + + UrlToVideoToTag = { + %(url_to_video("xml")) => %(http://www.example.com/videos/xml), + %(url_to_video("xml.ogg")) => %(http://www.example.com/videos/xml.ogg), + %(url_to_video("dir/xml.ogg")) => %(http://www.example.com/videos/dir/xml.ogg), + %(url_to_video("/dir/xml.ogg")) => %(http://www.example.com/dir/xml.ogg) + } + VideoLinkToTag = { - %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg" />), - %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v" />), - %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v" />), - %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160" />), - %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320" />), - %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg" />), - %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi" />), - %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi" />), - %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi" />), - %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov" />), - %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov" />), + %(video_tag("xml.ogg")) => %(<video src="/videos/xml.ogg"></video>), + %(video_tag("rss.m4v", :autoplay => true, :controls => true)) => %(<video autoplay="autoplay" controls="controls" src="/videos/rss.m4v"></video>), + %(video_tag("rss.m4v", :autobuffer => true)) => %(<video autobuffer="autobuffer" src="/videos/rss.m4v"></video>), + %(video_tag("gold.m4v", :size => "160x120")) => %(<video height="120" src="/videos/gold.m4v" width="160"></video>), + %(video_tag("gold.m4v", "size" => "320x240")) => %(<video height="240" src="/videos/gold.m4v" width="320"></video>), + %(video_tag("trailer.ogg", :poster => "screenshot.png")) => %(<video poster="/images/screenshot.png" src="/videos/trailer.ogg"></video>), + %(video_tag("error.avi", "size" => "100")) => %(<video src="/videos/error.avi"></video>), + %(video_tag("error.avi", "size" => "100 x 100")) => %(<video src="/videos/error.avi"></video>), + %(video_tag("error.avi", "size" => "x")) => %(<video src="/videos/error.avi"></video>), + %(video_tag("http://media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="http://media.rubyonrails.org/video/rails_blog_2.mov"></video>), + %(video_tag("//media.rubyonrails.org/video/rails_blog_2.mov")) => %(<video src="//media.rubyonrails.org/video/rails_blog_2.mov"></video>), %(video_tag("multiple.ogg", "multiple.avi")) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), %(video_tag(["multiple.ogg", "multiple.avi"])) => %(<video><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>), %(video_tag(["multiple.ogg", "multiple.avi"], :size => "160x120", :controls => true)) => %(<video controls="controls" height="120" width="160"><source src="/videos/multiple.ogg" /><source src="/videos/multiple.avi" /></video>) } - AudioPathToTag = { + AudioPathToTag = { %(audio_path("xml")) => %(/audios/xml), %(audio_path("xml.wav")) => %(/audios/xml.wav), %(audio_path("dir/xml.wav")) => %(/audios/dir/xml.wav), @@ -220,11 +275,25 @@ class AssetTagHelperTest < ActionView::TestCase %(path_to_audio("/dir/xml.wav")) => %(/dir/xml.wav) } + AudioUrlToTag = { + %(audio_url("xml")) => %(http://www.example.com/audios/xml), + %(audio_url("xml.wav")) => %(http://www.example.com/audios/xml.wav), + %(audio_url("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav), + %(audio_url("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav) + } + + UrlToAudioToTag = { + %(url_to_audio("xml")) => %(http://www.example.com/audios/xml), + %(url_to_audio("xml.wav")) => %(http://www.example.com/audios/xml.wav), + %(url_to_audio("dir/xml.wav")) => %(http://www.example.com/audios/dir/xml.wav), + %(url_to_audio("/dir/xml.wav")) => %(http://www.example.com/dir/xml.wav) + } + AudioLinkToTag = { - %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav" />), - %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav" />), - %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov" />), - %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov" />), + %(audio_tag("xml.wav")) => %(<audio src="/audios/xml.wav"></audio>), + %(audio_tag("rss.wav", :autoplay => true, :controls => true)) => %(<audio autoplay="autoplay" controls="controls" src="/audios/rss.wav"></audio>), + %(audio_tag("http://media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="http://media.rubyonrails.org/audio/rails_blog_2.mov"></audio>), + %(audio_tag("//media.rubyonrails.org/audio/rails_blog_2.mov")) => %(<audio src="//media.rubyonrails.org/audio/rails_blog_2.mov"></audio>), %(audio_tag("audio.mp3", "audio.ogg")) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), %(audio_tag(["audio.mp3", "audio.ogg"])) => %(<audio><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>), %(audio_tag(["audio.mp3", "audio.ogg"], :autobuffer => true, :controls => true)) => %(<audio autobuffer="autobuffer" controls="controls"><source src="/audios/audio.mp3" /><source src="/audios/audio.ogg" /></audio>) @@ -242,6 +311,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_javascript_url + JavascriptUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_javascript_alias_for_javascript_url + UrlToJavascriptToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_javascript_include_tag_with_blank_asset_id ENV["RAILS_ASSET_ID"] = "" JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } @@ -349,6 +426,15 @@ class AssetTagHelperTest < ActionView::TestCase PathToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_stylesheet_url + ENV["RAILS_ASSET_ID"] = "" + StyleUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_stylesheet_alias_for_stylesheet_url + UrlToStyleToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_stylesheet_link_tag ENV["RAILS_ASSET_ID"] = "" StyleLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } @@ -433,6 +519,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_image_url + ImageUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_image_alias_for_image_url + UrlToImageToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_image_alt [nil, '/', '/foo/bar/', 'foo/bar/'].each do |prefix| assert_equal 'Rails', image_alt("#{prefix}rails.png") @@ -478,6 +572,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_video_url + VideoUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_video_alias_for_video_url + UrlToVideoToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_video_tag VideoLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -490,6 +592,14 @@ class AssetTagHelperTest < ActionView::TestCase PathToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end + def test_audio_url + AudioUrlToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + + def test_url_to_audio_alias_for_audio_url + UrlToAudioToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } + end + def test_audio_tag AudioLinkToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) } end @@ -538,6 +648,13 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(<img alt="Rails" src="#{@controller.config.relative_url_root}/images/rails.png?#{expected_time}" />), image_tag("rails.png") end + # Same as above, but with script_name + def test_timebased_asset_id_with_script_name + @request.script_name = "/collaboration/hieraki" + expected_time = File.mtime(File.expand_path(File.dirname(__FILE__) + "/../fixtures/public/images/rails.png")).to_i.to_s + assert_equal %(<img alt="Rails" src="#{@request.script_name}/images/rails.png?#{expected_time}" />), image_tag("rails.png") + end + def test_should_skip_asset_id_on_complete_url assert_equal %(<img alt="Rails" src="http://www.example.com/rails.png" />), image_tag("http://www.example.com/rails.png") end @@ -573,7 +690,6 @@ class AssetTagHelperTest < ActionView::TestCase end end - @controller.request.stubs(:ssl?).returns(false) assert_equal "http://assets15.example.com/images/xml.png", image_path("xml.png") @@ -789,6 +905,31 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) end + # Same as above, but with script_name + def test_caching_javascript_include_tag_with_script_name + ENV["RAILS_ASSET_ID"] = "" + @request.script_name = "/collaboration/hieraki" + config.perform_caching = true + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/all.js" type="text/javascript"></script>), + javascript_include_tag(:all, :cache => true) + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/money.js" type="text/javascript"></script>), + javascript_include_tag(:all, :cache => "money") + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) + + ensure + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) + end + def test_caching_javascript_include_tag_with_named_paths_and_relative_url_root_when_caching_off ENV["RAILS_ASSET_ID"] = "" @controller.config.relative_url_root = "/collaboration/hieraki" @@ -809,6 +950,27 @@ class AssetTagHelperTest < ActionView::TestCase assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) end + # Same as above, but with script_name + def test_caching_javascript_include_tag_with_named_paths_and_script_name_when_caching_off + ENV["RAILS_ASSET_ID"] = "" + @request.script_name = "/collaboration/hieraki" + config.perform_caching = false + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>), + javascript_include_tag('robber', :cache => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'all.js')) + + assert_dom_equal( + %(<script src="/collaboration/hieraki/javascripts/robber.js" type="text/javascript"></script>), + javascript_include_tag('robber', :cache => "money", :recursive => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::JAVASCRIPTS_DIR, 'money.js')) + end + def test_caching_javascript_include_tag_when_caching_off ENV["RAILS_ASSET_ID"] = "" config.perform_caching = false @@ -1036,6 +1198,33 @@ class AssetTagHelperTest < ActionView::TestCase FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) end + # Same as above, but with script_name + def test_caching_stylesheet_link_tag_with_script_name + ENV["RAILS_ASSET_ID"] = "" + @request.script_name = "/collaboration/hieraki" + config.perform_caching = true + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag(:all, :cache => true) + ) + + files_to_be_joined = Dir["#{ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR}/[^all]*.css"] + + expected_mtime = files_to_be_joined.map { |p| File.mtime(p) }.max + assert_equal expected_mtime, File.mtime(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/money.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag(:all, :cache => "money") + ) + + assert File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) + ensure + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) + FileUtils.rm_f(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) + end + def test_caching_stylesheet_link_tag_with_named_paths_and_relative_url_root_when_caching_off ENV["RAILS_ASSET_ID"] = "" @@ -1057,6 +1246,27 @@ class AssetTagHelperTest < ActionView::TestCase assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) end + # Same as above, but with script_name + def test_caching_stylesheet_link_tag_with_named_paths_and_script_name_when_caching_off + ENV["RAILS_ASSET_ID"] = "" + @request.script_name = "/collaboration/hieraki" + config.perform_caching = false + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag('robber', :cache => true) + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'all.css')) + + assert_dom_equal( + %(<link href="/collaboration/hieraki/stylesheets/robber.css" media="screen" rel="stylesheet" type="text/css" />), + stylesheet_link_tag('robber', :cache => "money") + ) + + assert !File.exist?(File.join(ActionView::Helpers::AssetTagHelper::STYLESHEETS_DIR, 'money.css')) + end + @@ -1141,6 +1351,22 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase assert_dom_equal(%(<img alt="Mouse2" onmouseover="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse_over2.png'" onmouseout="this.src='gopher://assets.example.com/collaboration/hieraki/images/mouse2.png'" src="gopher://assets.example.com/collaboration/hieraki/images/mouse2.png" />), image_tag("mouse2.png", :mouseover => image_path("mouse_over2.png"))) end + def test_should_compute_proper_url_with_asset_host + @controller.config.asset_host = "assets.example.com" + assert_dom_equal(%(<link href="http://www.example.com/collaboration/hieraki" rel="alternate" title="RSS" type="application/rss+xml" />), auto_discovery_link_tag) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) + end + + def test_should_compute_proper_url_with_asset_host_and_default_protocol + @controller.config.asset_host = "assets.example.com" + @controller.config.default_asset_host_protocol = :request + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/javascripts/xmlhr.js), javascript_url("xmlhr")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/stylesheets/style.css), stylesheet_url("style")) + assert_dom_equal(%(gopher://assets.example.com/collaboration/hieraki/images/xml.png), image_url("xml.png")) + end + def test_should_ignore_asset_host_on_complete_url @controller.config.asset_host = "http://assets.example.com" assert_dom_equal(%(<link href="http://bar.example.com/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" />), stylesheet_link_tag("http://bar.example.com/stylesheets/style.css")) @@ -1154,16 +1380,19 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase def test_should_wildcard_asset_host_between_zero_and_four @controller.config.asset_host = 'http://a%d.example.com' assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png')) + assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_url('xml.png')) end def test_asset_host_without_protocol_should_be_protocol_relative @controller.config.asset_host = 'a.example.com' assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png') + assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_url('xml.png') end def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present @controller.config.asset_host = 'a.example.com/files/go/here' assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png') + assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_url('xml.png') end def test_assert_css_and_js_of_the_same_name_return_correct_extension diff --git a/actionpack/test/template/compressors_test.rb b/actionpack/test/template/compressors_test.rb deleted file mode 100644 index a273f15bd7..0000000000 --- a/actionpack/test/template/compressors_test.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'abstract_unit' -require 'sprockets/compressors' - -class CompressorsTest < ActiveSupport::TestCase - def test_register_css_compressor - Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor) - compressor = Sprockets::Compressors.registered_css_compressor(:null) - assert_kind_of Sprockets::NullCompressor, compressor - end - - def test_register_js_compressor - Sprockets::Compressors.register_js_compressor(:uglifier, 'Uglifier', :require => 'uglifier') - compressor = Sprockets::Compressors.registered_js_compressor(:uglifier) - assert_kind_of Uglifier, compressor - end - - def test_register_default_css_compressor - Sprockets::Compressors.register_css_compressor(:null, Sprockets::NullCompressor, :default => true) - compressor = Sprockets::Compressors.registered_css_compressor(:default) - assert_kind_of Sprockets::NullCompressor, compressor - end - - def test_register_default_js_compressor - Sprockets::Compressors.register_js_compressor(:null, Sprockets::NullCompressor, :default => true) - compressor = Sprockets::Compressors.registered_js_compressor(:default) - assert_kind_of Sprockets::NullCompressor, compressor - end -end diff --git a/actionpack/test/template/date_helper_test.rb b/actionpack/test/template/date_helper_test.rb index 9e2f4ec347..f622097b57 100644 --- a/actionpack/test/template/date_helper_test.rb +++ b/actionpack/test/template/date_helper_test.rb @@ -711,7 +711,7 @@ class DateHelperTest < ActionView::TestCase # Since the order is incomplete nothing will be shown expected = %(<input id="date_first_year" name="date[first][year]" type="hidden" value="2003" />\n) expected << %(<input id="date_first_month" name="date[first][month]" type="hidden" value="8" />\n) - expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="16" />\n) + expected << %(<input id="date_first_day" name="date[first][day]" type="hidden" value="1" />\n) assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), :start_year => 2003, :end_year => 2005, :prefix => "date[first]", :order => [:day]) end @@ -943,7 +943,7 @@ class DateHelperTest < ActionView::TestCase expected << "</select>\n" expected << %(<input type="hidden" id="date_first_month" name="date[first][month]" value="8" />\n) - expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="16" />\n) + expected << %(<input type="hidden" id="date_first_day" name="date[first][day]" value="1" />\n) assert_dom_equal expected, select_date(Time.mktime(2003, 8, 16), { :date_separator => " / ", :discard_month => true, :discard_day => true, :start_year => 2003, :end_year => 2005, :prefix => "date[first]"}) end @@ -1396,6 +1396,20 @@ class DateHelperTest < ActionView::TestCase assert_dom_equal expected, date_select("post", "written_on", :order => [ :month, :year ]) end + def test_date_select_without_day_and_month + @post = Post.new + @post.written_on = Date.new(2004, 2, 29) + + expected = "<input type=\"hidden\" id=\"post_written_on_2i\" name=\"post[written_on(2i)]\" value=\"2\" />\n" + expected << "<input type=\"hidden\" id=\"post_written_on_3i\" name=\"post[written_on(3i)]\" value=\"1\" />\n" + + expected << %{<select id="post_written_on_1i" name="post[written_on(1i)]">\n} + expected << %{<option value="1999">1999</option>\n<option value="2000">2000</option>\n<option value="2001">2001</option>\n<option value="2002">2002</option>\n<option value="2003">2003</option>\n<option value="2004" selected="selected">2004</option>\n<option value="2005">2005</option>\n<option value="2006">2006</option>\n<option value="2007">2007</option>\n<option value="2008">2008</option>\n<option value="2009">2009</option>\n} + expected << "</select>\n" + + assert_dom_equal expected, date_select("post", "written_on", :order => [ :year ]) + end + def test_date_select_without_day_with_separator @post = Post.new @post.written_on = Date.new(2004, 6, 15) @@ -2427,7 +2441,7 @@ class DateHelperTest < ActionView::TestCase 1999.upto(2009) { |i| expected << %(<option value="#{i}"#{' selected="selected"' if i == 2004}>#{i}</option>\n) } expected << "</select>\n" expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n} - expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n} + expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n} expected << " — " @@ -2448,7 +2462,7 @@ class DateHelperTest < ActionView::TestCase expected = %{<input type="hidden" id="post_updated_at_1i" name="post[updated_at(1i)]" value="2004" />\n} expected << %{<input type="hidden" id="post_updated_at_2i" name="post[updated_at(2i)]" value="6" />\n} - expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="15" />\n} + expected << %{<input type="hidden" id="post_updated_at_3i" name="post[updated_at(3i)]" value="1" />\n} expected << %{<select id="post_updated_at_4i" name="post[updated_at(4i)]">\n} 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } @@ -2467,7 +2481,7 @@ class DateHelperTest < ActionView::TestCase expected = %{<input type="hidden" id="post_updated_at_1i" disabled="disabled" name="post[updated_at(1i)]" value="2004" />\n} expected << %{<input type="hidden" id="post_updated_at_2i" disabled="disabled" name="post[updated_at(2i)]" value="6" />\n} - expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="15" />\n} + expected << %{<input type="hidden" id="post_updated_at_3i" disabled="disabled" name="post[updated_at(3i)]" value="1" />\n} expected << %{<select id="post_updated_at_4i" disabled="disabled" name="post[updated_at(4i)]">\n} 0.upto(23) { |i| expected << %(<option value="#{sprintf("%02d", i)}"#{' selected="selected"' if i == 15}>#{sprintf("%02d", i)}</option>\n) } @@ -2865,6 +2879,10 @@ class DateHelperTest < ActionView::TestCase assert_match(/<time.*>Right now<\/time>/, time_tag(Time.now, 'Right now')) end + def test_time_tag_with_given_block + assert_match(/<time.*><span>Right now<\/span><\/time>/, time_tag(Time.now){ '<span>Right now</span>'.html_safe }) + end + def test_time_tag_with_different_format time = Time.now expected = "<time datetime=\"#{time.xmlschema}\">#{I18n.l(time, :format => :short)}</time>" diff --git a/actionpack/test/template/form_collections_helper_test.rb b/actionpack/test/template/form_collections_helper_test.rb new file mode 100644 index 0000000000..4d878635ef --- /dev/null +++ b/actionpack/test/template/form_collections_helper_test.rb @@ -0,0 +1,327 @@ +require 'abstract_unit' + +class Category < Struct.new(:id, :name) +end + +class FormCollectionsHelperTest < ActionView::TestCase + def assert_no_select(selector, value = nil) + assert_select(selector, :text => value, :count => 0) + end + + def with_collection_radio_buttons(*args, &block) + @output_buffer = collection_radio_buttons(*args, &block) + end + + def with_collection_check_boxes(*args, &block) + @output_buffer = collection_check_boxes(*args, &block) + end + + # COLLECTION RADIO BUTTONS + test 'collection radio accepts a collection and generate inputs from value method' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s + + assert_select 'input[type=radio][value=true]#user_active_true' + assert_select 'input[type=radio][value=false]#user_active_false' + end + + test 'collection radio accepts a collection and generate inputs from label method' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s + + assert_select 'label[for=user_active_true]', 'true' + assert_select 'label[for=user_active_false]', 'false' + end + + test 'collection radio handles camelized collection values for labels correctly' do + with_collection_radio_buttons :user, :active, ['Yes', 'No'], :to_s, :to_s + + assert_select 'label[for=user_active_yes]', 'Yes' + assert_select 'label[for=user_active_no]', 'No' + end + + test 'colection radio should sanitize collection values for labels correctly' do + with_collection_radio_buttons :user, :name, ['$0.99', '$1.99'], :to_s, :to_s + assert_select 'label[for=user_name_099]', '$0.99' + assert_select 'label[for=user_name_199]', '$1.99' + end + + test 'collection radio accepts checked item' do + with_collection_radio_buttons :user, :active, [[1, true], [0, false]], :last, :first, :checked => true + + assert_select 'input[type=radio][value=true][checked=checked]' + assert_no_select 'input[type=radio][value=false][checked=checked]' + end + + test 'collection radio accepts multiple disabled items' do + collection = [[1, true], [0, false], [2, 'other']] + with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => [true, false] + + assert_select 'input[type=radio][value=true][disabled=disabled]' + assert_select 'input[type=radio][value=false][disabled=disabled]' + assert_no_select 'input[type=radio][value=other][disabled=disabled]' + end + + test 'collection radio accepts single disable item' do + collection = [[1, true], [0, false]] + with_collection_radio_buttons :user, :active, collection, :last, :first, :disabled => true + + assert_select 'input[type=radio][value=true][disabled=disabled]' + assert_no_select 'input[type=radio][value=false][disabled=disabled]' + end + + test 'collection radio accepts html options as input' do + collection = [[1, true], [0, false]] + with_collection_radio_buttons :user, :active, collection, :last, :first, {}, :class => 'special-radio' + + assert_select 'input[type=radio][value=true].special-radio#user_active_true' + assert_select 'input[type=radio][value=false].special-radio#user_active_false' + end + + test 'collection radio does not wrap input inside the label' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s + + assert_select 'input[type=radio] + label' + assert_no_select 'label input' + end + + test 'collection radio accepts a block to render the label as radio button wrapper' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| + b.label { b.radio_button } + end + + assert_select 'label[for=user_active_true] > input#user_active_true[type=radio]' + assert_select 'label[for=user_active_false] > input#user_active_false[type=radio]' + end + + test 'collection radio accepts a block to change the order of label and radio button' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| + b.label + b.radio_button + end + + assert_select 'label[for=user_active_true] + input#user_active_true[type=radio]' + assert_select 'label[for=user_active_false] + input#user_active_false[type=radio]' + end + + test 'collection radio with block helpers accept extra html options' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:class => "radio_button") + b.radio_button(:class => "radio_button") + end + + assert_select 'label.radio_button[for=user_active_true] + input#user_active_true.radio_button[type=radio]' + assert_select 'label.radio_button[for=user_active_false] + input#user_active_false.radio_button[type=radio]' + end + + test 'collection radio with block helpers allows access to current text and value' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:"data-value" => b.value) { b.radio_button + b.text } + end + + assert_select 'label[for=user_active_true][data-value=true]', 'true' do + assert_select 'input#user_active_true[type=radio]' + end + assert_select 'label[for=user_active_false][data-value=false]', 'false' do + assert_select 'input#user_active_false[type=radio]' + end + end + + test 'collection radio with block helpers allows access to the current object item in the collection to access extra properties' do + with_collection_radio_buttons :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:class => b.object) { b.radio_button + b.text } + end + + assert_select 'label.true[for=user_active_true]', 'true' do + assert_select 'input#user_active_true[type=radio]' + end + assert_select 'label.false[for=user_active_false]', 'false' do + assert_select 'input#user_active_false[type=radio]' + end + end + + test 'collection radio buttons with fields for' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + @output_buffer = fields_for(:post) do |p| + p.collection_radio_buttons :category_id, collection, :id, :name + end + + assert_select 'input#post_category_id_1[type=radio][value=1]' + assert_select 'input#post_category_id_2[type=radio][value=2]' + + assert_select 'label[for=post_category_id_1]', 'Category 1' + assert_select 'label[for=post_category_id_2]', 'Category 2' + 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')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name + + assert_select 'input#user_category_ids_1[type=checkbox][value=1]' + assert_select 'input#user_category_ids_2[type=checkbox][value=2]' + end + + test 'collection check boxes generates only one hidden field for the entire collection, to ensure something will be sent back to the server when posting an empty collection' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name + + assert_select "input[type=hidden][name='user[category_ids][]'][value=]", :count => 1 + end + + test 'collection check boxes accepts a collection and generate a serie of checkboxes with labels for label method' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + with_collection_check_boxes :user, :category_ids, collection, :id, :name + + assert_select 'label[for=user_category_ids_1]', 'Category 1' + assert_select 'label[for=user_category_ids_2]', 'Category 2' + end + + test 'collection check boxes handles camelized collection values for labels correctly' do + with_collection_check_boxes :user, :active, ['Yes', 'No'], :to_s, :to_s + + assert_select 'label[for=user_active_yes]', 'Yes' + assert_select 'label[for=user_active_no]', 'No' + end + + test 'colection check box should sanitize collection values for labels correctly' do + with_collection_check_boxes :user, :name, ['$0.99', '$1.99'], :to_s, :to_s + assert_select 'label[for=user_name_099]', '$0.99' + assert_select 'label[for=user_name_199]', '$1.99' + end + + test 'collection check boxes accepts selected values as :checked option' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => [1, 3] + + assert_select 'input[type=checkbox][value=1][checked=checked]' + assert_select 'input[type=checkbox][value=3][checked=checked]' + assert_no_select 'input[type=checkbox][value=2][checked=checked]' + end + + test 'collection check boxes accepts a single checked value' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :checked => 3 + + assert_select 'input[type=checkbox][value=3][checked=checked]' + assert_no_select 'input[type=checkbox][value=1][checked=checked]' + assert_no_select 'input[type=checkbox][value=2][checked=checked]' + end + + test 'collection check boxes accepts selected values as :checked option and override the model values' do + user = Struct.new(:category_ids).new(2) + collection = (1..3).map{|i| [i, "Category #{i}"] } + + @output_buffer = fields_for(:user, user) do |p| + p.collection_check_boxes :category_ids, collection, :first, :last, :checked => [1, 3] + end + + assert_select 'input[type=checkbox][value=1][checked=checked]' + assert_select 'input[type=checkbox][value=3][checked=checked]' + assert_no_select 'input[type=checkbox][value=2][checked=checked]' + end + + test 'collection check boxes accepts multiple disabled items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => [1, 3] + + assert_select 'input[type=checkbox][value=1][disabled=disabled]' + assert_select 'input[type=checkbox][value=3][disabled=disabled]' + assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' + end + + test 'collection check boxes accepts single disable item' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => 1 + + assert_select 'input[type=checkbox][value=1][disabled=disabled]' + assert_no_select 'input[type=checkbox][value=3][disabled=disabled]' + assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' + end + + test 'collection check boxes accepts a proc to disabled items' do + collection = (1..3).map{|i| [i, "Category #{i}"] } + with_collection_check_boxes :user, :category_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 } + + assert_select 'input[type=checkbox][value=1][disabled=disabled]' + assert_no_select 'input[type=checkbox][value=3][disabled=disabled]' + assert_no_select 'input[type=checkbox][value=2][disabled=disabled]' + end + + test 'collection check boxes accepts html options' do + collection = [[1, 'Category 1'], [2, 'Category 2']] + with_collection_check_boxes :user, :category_ids, collection, :first, :last, {}, :class => 'check' + + assert_select 'input.check[type=checkbox][value=1]' + assert_select 'input.check[type=checkbox][value=2]' + end + + test 'collection check boxes with fields for' do + collection = [Category.new(1, 'Category 1'), Category.new(2, 'Category 2')] + @output_buffer = fields_for(:post) do |p| + p.collection_check_boxes :category_ids, collection, :id, :name + end + + assert_select 'input#post_category_ids_1[type=checkbox][value=1]' + assert_select 'input#post_category_ids_2[type=checkbox][value=2]' + + assert_select 'label[for=post_category_ids_1]', 'Category 1' + assert_select 'label[for=post_category_ids_2]', 'Category 2' + end + + test 'collection check boxes does not wrap input inside the label' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s + + assert_select 'input[type=checkbox] + label' + assert_no_select 'label input' + end + + test 'collection check boxes accepts a block to render the label as check box wrapper' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| + b.label { b.check_box } + end + + assert_select 'label[for=user_active_true] > input#user_active_true[type=checkbox]' + assert_select 'label[for=user_active_false] > input#user_active_false[type=checkbox]' + end + + test 'collection check boxes accepts a block to change the order of label and check box' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| + b.label + b.check_box + end + + assert_select 'label[for=user_active_true] + input#user_active_true[type=checkbox]' + assert_select 'label[for=user_active_false] + input#user_active_false[type=checkbox]' + end + + test 'collection check boxes with block helpers accept extra html options' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:class => "check_box") + b.check_box(:class => "check_box") + end + + assert_select 'label.check_box[for=user_active_true] + input#user_active_true.check_box[type=checkbox]' + assert_select 'label.check_box[for=user_active_false] + input#user_active_false.check_box[type=checkbox]' + end + + test 'collection check boxes with block helpers allows access to current text and value' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:"data-value" => b.value) { b.check_box + b.text } + end + + assert_select 'label[for=user_active_true][data-value=true]', 'true' do + assert_select 'input#user_active_true[type=checkbox]' + end + assert_select 'label[for=user_active_false][data-value=false]', 'false' do + assert_select 'input#user_active_false[type=checkbox]' + end + end + + test 'collection check boxes with block helpers allows access to the current object item in the collection to access extra properties' do + with_collection_check_boxes :user, :active, [true, false], :to_s, :to_s do |b| + b.label(:class => b.object) { b.check_box + b.text } + end + + assert_select 'label.true[for=user_active_true]', 'true' do + assert_select 'input#user_active_true[type=checkbox]' + end + assert_select 'label.false[for=user_active_false]', 'false' do + assert_select 'input#user_active_false[type=checkbox]' + end + end +end diff --git a/actionpack/test/template/form_helper_test.rb b/actionpack/test/template/form_helper_test.rb index 4e440c6a13..7b684822fc 100644 --- a/actionpack/test/template/form_helper_test.rb +++ b/actionpack/test/template/form_helper_test.rb @@ -181,7 +181,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do + expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do "<label for=\"post_comments_attributes_0_body\">Write body here</label>" end @@ -198,7 +198,7 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do + expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do "<label for=\"post_tags_attributes_0_value\">Tag</label>" end @@ -249,35 +249,35 @@ class FormHelperTest < ActionView::TestCase end def test_label_with_block_in_erb - assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" size=\"30\" type=\"text\" />\n</label>", view.render("test/label_with_block") + assert_equal "<label for=\"post_message\">\n Message\n <input id=\"post_message\" name=\"post[message]\" type=\"text\" />\n</label>", view.render("test/label_with_block") end def test_text_field assert_dom_equal( - '<input id="post_title" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title") + '<input id="post_title" name="post[title]" type="text" value="Hello World" />', text_field("post", "title") ) assert_dom_equal( - '<input id="post_title" name="post[title]" size="30" type="password" />', password_field("post", "title") + '<input id="post_title" name="post[title]" type="password" />', password_field("post", "title") ) assert_dom_equal( - '<input id="post_title" name="post[title]" size="30" type="password" value="Hello World" />', password_field("post", "title", :value => @post.title) + '<input id="post_title" name="post[title]" type="password" value="Hello World" />', password_field("post", "title", :value => @post.title) ) assert_dom_equal( - '<input id="person_name" name="person[name]" size="30" type="password" />', password_field("person", "name") + '<input id="person_name" name="person[name]" type="password" />', password_field("person", "name") ) end def test_text_field_with_escapes @post.title = "<b>Hello World</b>" assert_dom_equal( - '<input id="post_title" name="post[title]" size="30" type="text" value="<b>Hello World</b>" />', text_field("post", "title") + '<input id="post_title" name="post[title]" type="text" value="<b>Hello World</b>" />', text_field("post", "title") ) end def test_text_field_with_html_entities @post.title = "The HTML Entity for & is &" assert_dom_equal( - '<input id="post_title" name="post[title]" size="30" type="text" value="The HTML Entity for & is &amp;" />', + '<input id="post_title" name="post[title]" type="text" value="The HTML Entity for & is &amp;" />', text_field("post", "title") ) end @@ -301,13 +301,18 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_with_nil_value - expected = '<input id="post_title" name="post[title]" size="30" type="text" />' + expected = '<input id="post_title" name="post[title]" type="text" />' assert_dom_equal expected, text_field("post", "title", :value => nil) end + def test_text_field_with_nil_name + expected = '<input id="post_title" type="text" value="Hello World" />' + assert_dom_equal expected, text_field("post", "title", :name => nil) + end + def test_text_field_doesnt_change_param_values object_name = 'post[]' - expected = '<input id="post_123_title" name="post[123][title]" size="30" type="text" value="Hello World" />' + expected = '<input id="post_123_title" name="post[123][title]" type="text" value="Hello World" />' assert_equal expected, text_field(object_name, "title") assert_equal object_name, "post[]" end @@ -341,7 +346,7 @@ class FormHelperTest < ActionView::TestCase end def test_text_field_with_custom_type - assert_dom_equal '<input id="user_email" size="30" name="user[email]" type="email" />', + assert_dom_equal '<input id="user_email" name="user[email]" type="email" />', text_field("user", "email", :type => "email") end @@ -382,6 +387,11 @@ class FormHelperTest < ActionView::TestCase ) end + def test_check_box_with_include_hidden_false + @post.secret = false + assert_dom_equal('<input id="post_secret" name="post[secret]" type="checkbox" value="1" />', check_box("post", "secret", :include_hidden => false)) + end + def test_check_box_with_explicit_checked_and_unchecked_values @post.secret = "on" assert_dom_equal( @@ -421,6 +431,13 @@ class FormHelperTest < ActionView::TestCase ) end + def test_checkbox_form_html5_attribute + assert_dom_equal( + '<input form="new_form" name="post[secret]" type="hidden" value="0" /><input checked="checked" form="new_form" id="post_secret" name="post[secret]" type="checkbox" value="1" />', + check_box("post", "secret", :form => "new_form") + ) + end + def test_radio_button assert_dom_equal('<input checked="checked" id="post_title_hello_world" name="post[title]" type="radio" value="Hello World" />', radio_button("post", "title", "Hello World") @@ -462,7 +479,7 @@ class FormHelperTest < ActionView::TestCase def test_text_area assert_dom_equal( - '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea id="post_body" name="post[body]">\nBack to the hill and over it again!</textarea>}, text_area("post", "body") ) end @@ -470,14 +487,14 @@ class FormHelperTest < ActionView::TestCase def test_text_area_with_escapes @post.body = "Back to <i>the</i> hill and over it again!" assert_dom_equal( - '<textarea cols="40" id="post_body" name="post[body]" rows="20">Back to <i>the</i> hill and over it again!</textarea>', + %{<textarea id="post_body" name="post[body]">\nBack to <i>the</i> hill and over it again!</textarea>}, text_area("post", "body") ) end def test_text_area_with_alternate_value assert_dom_equal( - '<textarea cols="40" id="post_body" name="post[body]" rows="20">Testing alternate values.</textarea>', + %{<textarea id="post_body" name="post[body]">\nTesting alternate values.</textarea>}, text_area("post", "body", :value => 'Testing alternate values.') ) end @@ -485,35 +502,61 @@ class FormHelperTest < ActionView::TestCase def test_text_area_with_html_entities @post.body = "The HTML Entity for & is &" assert_dom_equal( - '<textarea cols="40" id="post_body" name="post[body]" rows="20">The HTML Entity for & is &amp;</textarea>', + %{<textarea id="post_body" name="post[body]">\nThe HTML Entity for & is &amp;</textarea>}, text_area("post", "body") ) end def test_text_area_with_size_option assert_dom_equal( - '<textarea cols="183" id="post_body" name="post[body]" rows="820">Back to the hill and over it again!</textarea>', + %{<textarea cols="183" id="post_body" name="post[body]" rows="820">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", :size => "183x820") ) end def test_search_field - expected = %{<input id="contact_notes_query" size="30" name="contact[notes_query]" type="search" />} + expected = %{<input id="contact_notes_query" name="contact[notes_query]" type="search" />} assert_dom_equal(expected, search_field("contact", "notes_query")) end def test_telephone_field - expected = %{<input id="user_cell" size="30" name="user[cell]" type="tel" />} + expected = %{<input id="user_cell" name="user[cell]" type="tel" />} assert_dom_equal(expected, telephone_field("user", "cell")) end + def test_date_field + expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} + assert_dom_equal(expected, date_field("post", "written_on")) + end + + def test_date_field_with_datetime_value + expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} + @post.written_on = DateTime.new(2004, 6, 15, 1, 2, 3) + assert_dom_equal(expected, date_field("post", "written_on")) + end + + def test_date_field_with_timewithzone_value + previous_time_zone, Time.zone = Time.zone, 'UTC' + expected = %{<input id="post_written_on" name="post[written_on]" type="date" value="2004-06-15" />} + @post.written_on = Time.zone.parse('2004-06-15 15:30:45') + assert_dom_equal(expected, date_field("post", "written_on")) + ensure + Time.zone = previous_time_zone + end + + def test_date_field_with_nil_value + expected = %{<input id="post_written_on" name="post[written_on]" type="date" />} + @post.written_on = nil + assert_dom_equal(expected, date_field("post", "written_on")) + end + def test_url_field - expected = %{<input id="user_homepage" size="30" name="user[homepage]" type="url" />} + expected = %{<input id="user_homepage" name="user[homepage]" type="url" />} assert_dom_equal(expected, url_field("user", "homepage")) end def test_email_field - expected = %{<input id="user_address" size="30" name="user[address]" type="email" />} + expected = %{<input id="user_address" name="user[address]" type="email" />} assert_dom_equal(expected, email_field("user", "address")) end @@ -533,10 +576,10 @@ class FormHelperTest < ActionView::TestCase def test_explicit_name assert_dom_equal( - '<input id="post_title" name="dont guess" size="30" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess") + '<input id="post_title" name="dont guess" type="text" value="Hello World" />', text_field("post", "title", "name" => "dont guess") ) assert_dom_equal( - '<textarea cols="40" id="post_body" name="really!" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea id="post_body" name="really!">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", "name" => "really!") ) assert_dom_equal( @@ -553,10 +596,10 @@ class FormHelperTest < ActionView::TestCase def test_explicit_id assert_dom_equal( - '<input id="dont guess" name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess") + '<input id="dont guess" name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => "dont guess") ) assert_dom_equal( - '<textarea cols="40" id="really!" name="post[body]" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea id="really!" name="post[body]">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", "id" => "really!") ) assert_dom_equal( @@ -573,10 +616,10 @@ class FormHelperTest < ActionView::TestCase def test_nil_id assert_dom_equal( - '<input name="post[title]" size="30" type="text" value="Hello World" />', text_field("post", "title", "id" => nil) + '<input name="post[title]" type="text" value="Hello World" />', text_field("post", "title", "id" => nil) ) assert_dom_equal( - '<textarea cols="40" name="post[body]" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea name="post[body]">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", "id" => nil) ) assert_dom_equal( @@ -603,11 +646,11 @@ class FormHelperTest < ActionView::TestCase def test_index assert_dom_equal( - '<input name="post[5][title]" size="30" id="post_5_title" type="text" value="Hello World" />', + '<input name="post[5][title]" id="post_5_title" type="text" value="Hello World" />', text_field("post", "title", "index" => 5) ) assert_dom_equal( - '<textarea cols="40" name="post[5][body]" id="post_5_body" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea name="post[5][body]" id="post_5_body">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", "index" => 5) ) assert_dom_equal( @@ -630,11 +673,11 @@ class FormHelperTest < ActionView::TestCase def test_index_with_nil_id assert_dom_equal( - '<input name="post[5][title]" size="30" type="text" value="Hello World" />', + '<input name="post[5][title]" type="text" value="Hello World" />', text_field("post", "title", "index" => 5, 'id' => nil) ) assert_dom_equal( - '<textarea cols="40" name="post[5][body]" rows="20">Back to the hill and over it again!</textarea>', + %{<textarea name="post[5][body]">\nBack to the hill and over it again!</textarea>}, text_area("post", "body", "index" => 5, 'id' => nil) ) assert_dom_equal( @@ -662,10 +705,10 @@ class FormHelperTest < ActionView::TestCase label("post[]", "title") ) assert_dom_equal( - "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title") + "<input id=\"post_#{pid}_title\" name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title") ) assert_dom_equal( - "<textarea cols=\"40\" id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>", + "<textarea id=\"post_#{pid}_body\" name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>", text_area("post[]", "body") ) assert_dom_equal( @@ -673,7 +716,7 @@ class FormHelperTest < ActionView::TestCase check_box("post[]", "secret") ) assert_dom_equal( -"<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />", + "<input checked=\"checked\" id=\"post_#{pid}_title_hello_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Hello World\" />", radio_button("post[]", "title", "Hello World") ) assert_dom_equal("<input id=\"post_#{pid}_title_goodbye_world\" name=\"post[#{pid}][title]\" type=\"radio\" value=\"Goodbye World\" />", @@ -684,11 +727,11 @@ class FormHelperTest < ActionView::TestCase def test_auto_index_with_nil_id pid = 123 assert_dom_equal( - "<input name=\"post[#{pid}][title]\" size=\"30\" type=\"text\" value=\"Hello World\" />", + "<input name=\"post[#{pid}][title]\" type=\"text\" value=\"Hello World\" />", text_field("post[]","title", :id => nil) ) assert_dom_equal( - "<textarea cols=\"40\" name=\"post[#{pid}][body]\" rows=\"20\">Back to the hill and over it again!</textarea>", + "<textarea name=\"post[#{pid}][body]\">\nBack to the hill and over it again!</textarea>", text_area("post[]", "body", :id => nil) ) assert_dom_equal( @@ -720,10 +763,10 @@ class FormHelperTest < ActionView::TestCase concat f.button('Create post') end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put") do + expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch') do "<label for='post_title'>The Title</label>" + - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" + "<input name='commit' type='submit' value='Create post' />" + @@ -733,6 +776,44 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_collection_radio_buttons + post = Post.new + def post.active; false; end + form_for(post) do |f| + concat f.collection_radio_buttons(:active, [true, false], :to_s, :to_s) + end + + expected = whole_form("/posts", "new_post" , "new_post") do + "<input id='post_active_true' name='post[active]' type='radio' value='true' />" + + "<label for='post_active_true'>true</label>" + + "<input checked='checked' id='post_active_false' name='post[active]' type='radio' value='false' />" + + "<label for='post_active_false'>false</label>" + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_with_collection_check_boxes + post = Post.new + def post.tag_ids; [1, 3]; end + collection = (1..3).map{|i| [i, "Tag #{i}"] } + form_for(post) do |f| + concat f.collection_check_boxes(:tag_ids, collection, :first, :last) + end + + expected = whole_form("/posts", "new_post" , "new_post") do + "<input checked='checked' id='post_tag_ids_1' name='post[tag_ids][]' type='checkbox' value='1' />" + + "<label for='post_tag_ids_1'>Tag 1</label>" + + "<input id='post_tag_ids_2' name='post[tag_ids][]' type='checkbox' value='2' />" + + "<label for='post_tag_ids_2'>Tag 2</label>" + + "<input checked='checked' id='post_tag_ids_3' name='post[tag_ids][]' type='checkbox' value='3' />" + + "<label for='post_tag_ids_3'>Tag 3</label>" + + "<input name='post[tag_ids][]' type='hidden' value='' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_file_field_generate_multipart Post.send :attr_accessor, :file @@ -740,7 +821,7 @@ class FormHelperTest < ActionView::TestCase concat f.file_field(:file) end - expected = whole_form("/posts/123", "create-post" , "edit_post", :method => "put", :multipart => true) do + expected = whole_form("/posts/123", "create-post" , "edit_post", :method => 'patch', :multipart => true) do "<input name='post[file]' type='file' id='post_file' />" end @@ -756,7 +837,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => "put", :multipart => true) do + expected = whole_form("/posts/123", "edit_post_123" , "edit_post", :method => 'patch', :multipart => true) do "<input name='post[comment][file]' type='file' id='post_comment_file' />" end @@ -769,7 +850,7 @@ class FormHelperTest < ActionView::TestCase concat f.label(:title) end - expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => "put") do + expected = whole_form("/posts/123.json", "edit_post_123" , "edit_post", :method => 'patch') do "<label for='post_title'>Title</label>" end @@ -782,8 +863,8 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Edit post') end - expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => "put") do - "<input name='post[title]' size='30' type='text' id='post_title' value='And his name will be forty and four.' />" + + expected = whole_form("/posts/44", "edit_post_44" , "edit_post", :method => 'patch') do + "<input name='post[title]' type='text' id='post_title' value='And his name will be forty and four.' />" + "<input name='commit' type='submit' value='Edit post' />" end @@ -799,10 +880,10 @@ class FormHelperTest < ActionView::TestCase concat f.submit('Create post') end - expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => "put") do + expected = whole_form("/posts/123", "create-post", "edit_other_name", :method => 'patch') do "<label for='other_name_title' class='post_title'>Title</label>" + - "<input name='other_name[title]' size='30' id='other_name_title' value='Hello World' type='text' />" + - "<textarea name='other_name[body]' id='other_name_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='other_name[title]' id='other_name_title' value='Hello World' type='text' />" + + "<textarea name='other_name[body]' id='other_name_body'>\nBack to the hill and over it again!</textarea>" + "<input name='other_name[secret]' value='0' type='hidden' />" + "<input name='other_name[secret]' checked='checked' id='other_name_secret' value='1' type='checkbox' />" + "<input name='commit' value='Create post' type='submit' />" @@ -819,8 +900,8 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/", "create-post", "edit_post", "delete") do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -836,8 +917,8 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/", "create-post", "edit_post", "delete") do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -853,22 +934,22 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/search", "search-post", "new_post", "get") do - "<input name='post[title]' size='30' type='search' id='post_title' />" + "<input name='post[title]' type='search' id='post_title' />" end assert_dom_equal expected, output_buffer end def test_form_for_with_remote - form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| + form_for(@post, :url => '/', :remote => true, :html => { :id => 'create-post', :method => :patch}) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -877,15 +958,15 @@ class FormHelperTest < ActionView::TestCase end def test_form_for_with_remote_in_html - form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :put }) do |f| + form_for(@post, :url => '/', :html => { :remote => true, :id => 'create-post', :method => :patch }) do |f| concat f.text_field(:title) concat f.text_area(:body) concat f.check_box(:secret) end - expected = whole_form("/", "create-post", "edit_post", :method => "put", :remote => true) do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + expected = whole_form("/", "create-post", "edit_post", :method => 'patch', :remote => true) do + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -903,8 +984,8 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/posts", 'new_post', 'new_post', :remote => true) do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -920,8 +1001,8 @@ class FormHelperTest < ActionView::TestCase end expected = whole_form("/", "create-post") do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" end @@ -937,10 +1018,10 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do "<label for='post_123_title'>Title</label>" + - "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + - "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + + "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[123][secret]' type='hidden' value='0' />" + "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" end @@ -955,9 +1036,9 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do - "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" + - "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + + "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" end @@ -965,6 +1046,40 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_label_error_wrapping + form_for(@post) do |f| + concat f.label(:author_name, :class => 'label') + concat f.text_field(:author_name) + concat f.submit('Create post') + end + + expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do + "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + + "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + + "<input name='commit' type='submit' value='Create post' />" + end + + assert_dom_equal expected, output_buffer + end + + def test_form_for_label_error_wrapping_without_conventional_instance_variable + post = remove_instance_variable :@post + + form_for(post) do |f| + concat f.label(:author_name, :class => 'label') + concat f.text_field(:author_name) + concat f.submit('Create post') + end + + expected = whole_form('/posts/123', 'edit_post_123' , 'edit_post', 'patch') do + "<div class='field_with_errors'><label for='post_author_name' class='label'>Author name</label></div>" + + "<div class='field_with_errors'><input name='post[author_name]' type='text' id='post_author_name' value='' /></div>" + + "<input name='commit' type='submit' value='Create post' />" + end + + assert_dom_equal expected, output_buffer + end + def test_form_for_with_namespace form_for(@post, :namespace => 'namespace') do |f| concat f.text_field(:title) @@ -972,9 +1087,9 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do - "<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />" + - "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do + "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + + "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='namespace_post_secret' value='1' />" end @@ -982,15 +1097,23 @@ class FormHelperTest < ActionView::TestCase assert_dom_equal expected, output_buffer end + def test_form_for_with_namespace_with_date_select + form_for(@post, :namespace => 'namespace') do |f| + concat f.date_select(:written_on) + end + + assert_select 'select#namespace_post_written_on_1i' + end + def test_form_for_with_namespace_with_label form_for(@post, :namespace => 'namespace') do |f| concat f.label(:title) concat f.text_field(:title) end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do "<label for='namespace_post_title'>Title</label>" + - "<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />" + "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1002,9 +1125,9 @@ class FormHelperTest < ActionView::TestCase concat f.text_field(:title) end - expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'put') do + expected_1 = whole_form('/posts/123', 'namespace_1_edit_post_123', 'edit_post', 'patch') do "<label for='namespace_1_post_title'>Title</label>" + - "<input name='post[title]' size='30' type='text' id='namespace_1_post_title' value='Hello World' />" + "<input name='post[title]' type='text' id='namespace_1_post_title' value='Hello World' />" end assert_dom_equal expected_1, output_buffer @@ -1014,9 +1137,9 @@ class FormHelperTest < ActionView::TestCase concat f.text_field(:title) end - expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'put') do + expected_2 = whole_form('/posts/123', 'namespace_2_edit_post_123', 'edit_post', 'patch') do "<label for='namespace_2_post_title'>Title</label>" + - "<input name='post[title]' size='30' type='text' id='namespace_2_post_title' value='Hello World' />" + "<input name='post[title]' type='text' id='namespace_2_post_title' value='Hello World' />" end assert_dom_equal expected_2, output_buffer @@ -1032,10 +1155,10 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'put') do - "<input name='post[title]' size='30' type='text' id='namespace_post_title' value='Hello World' />" + - "<textarea name='post[body]' id='namespace_post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + - "<input name='post[comment][body]' size='30' type='text' id='namespace_post_comment_body' value='Hello World' />" + expected = whole_form('/posts/123', 'namespace_edit_post_123', 'edit_post', 'patch') do + "<input name='post[title]' type='text' id='namespace_post_title' value='Hello World' />" + + "<textarea name='post[body]' id='namespace_post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[comment][body]' type='text' id='namespace_post_comment_body' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1066,7 +1189,7 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do "<input name='commit' type='submit' value='Confirm Post changes' />" end @@ -1098,7 +1221,7 @@ class FormHelperTest < ActionView::TestCase concat f.submit end - expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'put') do + expected = whole_form('/posts/123', 'edit_another_post', 'edit_another_post', :method => 'patch') do "<input name='commit' type='submit' value='Update your Post' />" end @@ -1115,8 +1238,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - "<input name='post[comment][body]' size='30' type='text' id='post_comment_body' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + "<input name='post[comment][body]' type='text' id='post_comment_body' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1130,9 +1253,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do - "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + - "<input name='post[123][comment][][name]' size='30' type='text' id='post_123_comment__name' value='new comment' />" + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + + "<input name='post[123][comment][][name]' type='text' id='post_123_comment__name' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -1146,9 +1269,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[1][title]' size='30' type='text' id='post_1_title' value='Hello World' />" + - "<input name='post[1][comment][1][name]' size='30' type='text' id='post_1_comment_1_name' value='new comment' />" + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + "<input name='post[1][title]' type='text' id='post_1_title' value='Hello World' />" + + "<input name='post[1][comment][1][name]' type='text' id='post_1_comment_1_name' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -1161,8 +1284,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[1][comment][title]' size='30' type='text' id='post_1_comment_title' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + "<input name='post[1][comment][title]' type='text' id='post_1_comment_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1175,8 +1298,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[1][comment][5][title]' size='30' type='text' id='post_1_comment_5_title' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + "<input name='post[1][comment][5][title]' type='text' id='post_1_comment_5_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1189,8 +1312,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do - "<input name='post[123][comment][title]' size='30' type='text' id='post_123_comment_title' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + "<input name='post[123][comment][title]' type='text' id='post_123_comment_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1203,7 +1326,7 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do "<input name='post[comment][5][title]' type='radio' id='post_comment_5_title_hello' value='hello' />" end @@ -1217,8 +1340,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do - "<input name='post[123][comment][123][title]' size='30' type='text' id='post_123_comment_123_title' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + "<input name='post[123][comment][123][title]' type='text' id='post_123_comment_123_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1237,10 +1360,10 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'put') do - "<input name='post[123][comment][5][title]' size='30' type='text' id='post_123_comment_5_title' value='Hello World' />" - end + whole_form('/posts/123', 'edit_post', 'edit_post', 'put') do - "<input name='post[1][comment][123][title]' size='30' type='text' id='post_1_comment_123_title' value='Hello World' />" + expected = whole_form('/posts/123', 'edit_post[]', 'edit_post[]', 'patch') do + "<input name='post[123][comment][5][title]' type='text' id='post_123_comment_5_title' value='Hello World' />" + end + whole_form('/posts/123', 'edit_post', 'edit_post', 'patch') do + "<input name='post[1][comment][123][title]' type='text' id='post_1_comment_123_title' value='Hello World' />" end assert_dom_equal expected, output_buffer @@ -1256,9 +1379,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="new author" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="new author" />' end assert_dom_equal expected, output_buffer @@ -1283,9 +1406,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' end @@ -1302,9 +1425,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' end @@ -1321,9 +1444,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1339,9 +1462,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1357,9 +1480,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' end @@ -1377,10 +1500,10 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' end assert_dom_equal expected, output_buffer @@ -1398,11 +1521,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' end @@ -1425,12 +1548,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1452,11 +1575,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1478,12 +1601,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="author #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="author #321" />' + '<input id="post_author_attributes_id" name="post[author_attributes][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1501,11 +1624,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' end @@ -1525,12 +1648,12 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' end assert_dom_equal expected, output_buffer @@ -1548,10 +1671,10 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="new comment" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="new comment" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1569,11 +1692,11 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1587,8 +1710,8 @@ class FormHelperTest < ActionView::TestCase end end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' end assert_dom_equal expected, output_buffer @@ -1604,11 +1727,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' end @@ -1625,11 +1748,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' end @@ -1647,11 +1770,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #1" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #1" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="1" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="comment #2" />' + + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="comment #2" />' + '<input id="post_comments_attributes_1_id" name="post[comments_attributes][1][id]" type="hidden" value="2" />' end @@ -1670,11 +1793,11 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input name="post[title]" size="30" type="text" id="post_title" value="Hello World" />' + - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input name="post[title]" type="text" id="post_title" value="Hello World" />' + + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" size="30" type="text" value="new comment" />' + '<input id="post_comments_attributes_1_name" name="post[comments_attributes][1][name]" type="text" value="new comment" />' end assert_dom_equal expected, output_buffer @@ -1690,8 +1813,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" size="30" type="text" value="comment #321" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input id="post_comments_attributes_abc_name" name="post[comments_attributes][abc][name]" type="text" value="comment #321" />' + '<input id="post_comments_attributes_abc_id" name="post[comments_attributes][abc][id]" type="hidden" value="321" />' end @@ -1726,17 +1849,17 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" size="30" type="text" value="comment #321" />' + - '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="commentrelevance #314" />' + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input id="post_comments_attributes_0_name" name="post[comments_attributes][0][name]" type="text" value="comment #321" />' + + '<input id="post_comments_attributes_0_relevances_attributes_0_value" name="post[comments_attributes][0][relevances_attributes][0][value]" type="text" value="commentrelevance #314" />' + '<input id="post_comments_attributes_0_relevances_attributes_0_id" name="post[comments_attributes][0][relevances_attributes][0][id]" type="hidden" value="314" />' + '<input id="post_comments_attributes_0_id" name="post[comments_attributes][0][id]" type="hidden" value="321" />' + - '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" size="30" type="text" value="tag #123" />' + - '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #3141" />' + + '<input id="post_tags_attributes_0_value" name="post[tags_attributes][0][value]" type="text" value="tag #123" />' + + '<input id="post_tags_attributes_0_relevances_attributes_0_value" name="post[tags_attributes][0][relevances_attributes][0][value]" type="text" value="tagrelevance #3141" />' + '<input id="post_tags_attributes_0_relevances_attributes_0_id" name="post[tags_attributes][0][relevances_attributes][0][id]" type="hidden" value="3141" />' + '<input id="post_tags_attributes_0_id" name="post[tags_attributes][0][id]" type="hidden" value="123" />' + - '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" size="30" type="text" value="tag #456" />' + - '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" size="30" type="text" value="tagrelevance #31415" />' + + '<input id="post_tags_attributes_1_value" name="post[tags_attributes][1][value]" type="text" value="tag #456" />' + + '<input id="post_tags_attributes_1_relevances_attributes_0_value" name="post[tags_attributes][1][relevances_attributes][0][value]" type="text" value="tagrelevance #31415" />' + '<input id="post_tags_attributes_1_relevances_attributes_0_id" name="post[tags_attributes][1][relevances_attributes][0][id]" type="hidden" value="31415" />' + '<input id="post_tags_attributes_1_id" name="post[tags_attributes][1][id]" type="hidden" value="456" />' end @@ -1753,8 +1876,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - '<input id="post_author_attributes_name" name="post[author_attributes][name]" size="30" type="text" value="hash backed author" />' + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + '<input id="post_author_attributes_name" name="post[author_attributes][name]" type="text" value="hash backed author" />' end assert_dom_equal expected, output_buffer @@ -1768,8 +1891,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" @@ -1784,8 +1907,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[123][title]' size='30' type='text' id='post_123_title' value='Hello World' />" + - "<textarea name='post[123][body]' id='post_123_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[123][title]' type='text' id='post_123_title' value='Hello World' />" + + "<textarea name='post[123][body]' id='post_123_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[123][secret]' type='hidden' value='0' />" + "<input name='post[123][secret]' checked='checked' type='checkbox' id='post_123_secret' value='1' />" @@ -1800,8 +1923,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[][title]' size='30' type='text' id='post__title' value='Hello World' />" + - "<textarea name='post[][body]' id='post__body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[][title]' type='text' id='post__title' value='Hello World' />" + + "<textarea name='post[][body]' id='post__body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[][secret]' type='hidden' value='0' />" + "<input name='post[][secret]' checked='checked' type='checkbox' id='post__secret' value='1' />" @@ -1816,8 +1939,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[abc][title]' size='30' type='text' id='post_abc_title' value='Hello World' />" + - "<textarea name='post[abc][body]' id='post_abc_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[abc][title]' type='text' id='post_abc_title' value='Hello World' />" + + "<textarea name='post[abc][body]' id='post_abc_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[abc][secret]' type='hidden' value='0' />" + "<input name='post[abc][secret]' checked='checked' type='checkbox' id='post_abc_secret' value='1' />" @@ -1832,8 +1955,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" @@ -1848,8 +1971,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='post[secret]' type='hidden' value='0' />" + "<input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' />" @@ -1863,7 +1986,7 @@ class FormHelperTest < ActionView::TestCase end assert_dom_equal "<label for=\"author_post_title\">Title</label>" + - "<input name='author[post][title]' size='30' type='text' id='author_post_title' value='Hello World' />", + "<input name='author[post][title]' type='text' id='author_post_title' value='Hello World' />", output_buffer end @@ -1874,7 +1997,7 @@ class FormHelperTest < ActionView::TestCase end assert_dom_equal "<label for=\"author_post_1_title\">Title</label>" + - "<input name='author[post][1][title]' size='30' type='text' id='author_post_1_title' value='Hello World' />", + "<input name='author[post][1][title]' type='text' id='author_post_1_title' value='Hello World' />", output_buffer end @@ -1892,9 +2015,9 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'put') do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + + expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + "<input name='parent_post[secret]' type='hidden' value='0' />" + "<input name='parent_post[secret]' checked='checked' type='checkbox' id='parent_post_secret' value='1' />" end @@ -1912,10 +2035,10 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'put') do - "<input name='post[title]' size='30' type='text' id='post_title' value='Hello World' />" + - "<textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea>" + - "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' size='30' />" + expected = whole_form('/posts/123', 'create-post', 'edit_post', :method => 'patch') do + "<input name='post[title]' type='text' id='post_title' value='Hello World' />" + + "<textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea>" + + "<input name='post[comment][name]' type='text' id='post_comment_name' value='new comment' />" end assert_dom_equal expected, output_buffer @@ -1928,8 +2051,8 @@ class FormHelperTest < ActionView::TestCase } end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'put') do - "<input name='post[category][name]' type='text' size='30' id='post_category_name' />" + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', 'patch') do + "<input name='post[category][name]' type='text' id='post_category_name' />" end assert_dom_equal expected, output_buffer @@ -1952,46 +2075,15 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" end assert_dom_equal expected, output_buffer end - def hidden_fields(method = nil) - txt = %{<div style="margin:0;padding:0;display:inline">} - txt << %{<input name="utf8" type="hidden" value="✓" />} - if method && !method.to_s.in?(['get', 'post']) - txt << %{<input name="_method" type="hidden" value="#{method}" />} - end - txt << %{</div>} - end - - def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) - txt = %{<form accept-charset="UTF-8" action="#{action}"} - txt << %{ enctype="multipart/form-data"} if multipart - txt << %{ data-remote="true"} if remote - txt << %{ class="#{html_class}"} if html_class - txt << %{ id="#{id}"} if id - method = method.to_s == "get" ? "get" : "post" - txt << %{ method="#{method}">} - end - - def whole_form(action = "/", id = nil, html_class = nil, options = nil) - contents = block_given? ? yield : "" - - if options.is_a?(Hash) - method, remote, multipart = options.values_at(:method, :remote, :multipart) - else - method = options - end - - form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" - end - def test_default_form_builder old_default_form_builder, ActionView::Base.default_form_builder = ActionView::Base.default_form_builder, LabelledFormBuilder @@ -2002,9 +2094,9 @@ class FormHelperTest < ActionView::TestCase concat f.check_box(:secret) end - expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'put') do - "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + + expected = whole_form('/posts/123', 'edit_post_123', 'edit_post', :method => 'patch') do + "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" end @@ -2021,8 +2113,8 @@ class FormHelperTest < ActionView::TestCase end expected = - "<label for='title'>Title:</label> <input name='post[title]' size='30' type='text' id='post_title' value='Hello World' /><br/>" + - "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body' rows='20' cols='40'>Back to the hill and over it again!</textarea><br/>" + + "<label for='title'>Title:</label> <input name='post[title]' type='text' id='post_title' value='Hello World' /><br/>" + + "<label for='body'>Body:</label> <textarea name='post[body]' id='post_body'>\nBack to the hill and over it again!</textarea><br/>" + "<label for='secret'>Secret:</label> <input name='post[secret]' type='hidden' value='0' /><input name='post[secret]' checked='checked' type='checkbox' id='post_secret' value='1' /><br/>" assert_dom_equal expected, output_buffer @@ -2082,7 +2174,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_html_options_adds_options_to_form_tag form_for(@post, :html => {:id => 'some_form', :class => 'some_class'}) do |f| end - expected = whole_form("/posts/123", "some_form", "some_class", 'put') + expected = whole_form("/posts/123", "some_form", "some_class", 'patch') assert_dom_equal expected, output_buffer end @@ -2090,7 +2182,7 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_string_url_option form_for(@post, :url => 'http://www.otherdomain.com') do |f| end - assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'put'), output_buffer + assert_equal whole_form("http://www.otherdomain.com", 'edit_post_123', 'edit_post', 'patch'), output_buffer end def test_form_for_with_hash_url_option @@ -2103,14 +2195,14 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_record_url_option form_for(@post, :url => @post) do |f| end - expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'put') + expected = whole_form("/posts/123", 'edit_post_123', 'edit_post', 'patch') assert_equal expected, output_buffer end def test_form_for_with_existing_object form_for(@post) do |f| end - expected = whole_form("/posts/123", "edit_post_123", "edit_post", "put") + expected = whole_form("/posts/123", "edit_post_123", "edit_post", 'patch') assert_equal expected, output_buffer end @@ -2129,7 +2221,7 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([@post, @comment]) {} - expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") + expected = whole_form(post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch') assert_dom_equal expected, output_buffer end @@ -2144,7 +2236,7 @@ class FormHelperTest < ActionView::TestCase @comment.save form_for([:admin, @post, @comment]) {} - expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", "put") + expected = whole_form(admin_post_comment_path(@post, @comment), "edit_comment_1", "edit_comment", 'patch') assert_dom_equal expected, output_buffer end @@ -2158,18 +2250,67 @@ class FormHelperTest < ActionView::TestCase def test_form_for_with_existing_object_and_custom_url form_for(@post, :url => "/super_posts") do |f| end - expected = whole_form("/super_posts", "edit_post_123", "edit_post", "put") + expected = whole_form("/super_posts", "edit_post_123", "edit_post", 'patch') assert_equal expected, output_buffer end + def test_form_for_with_default_method_as_patch + form_for(@post) {} + expected = whole_form("/posts/123", "edit_post_123", "edit_post", "patch") + assert_dom_equal expected, output_buffer + end + def test_fields_for_returns_block_result output = fields_for(Post.new) { |f| "fields" } assert_equal "fields", output end + def test_form_builder_block_argument_deprecation + builder_class = Class.new(ActionView::Helpers::FormBuilder) do + def initialize(object_name, object, template, options, block) + super + end + end + + assert_deprecated(/Giving a block to FormBuilder is deprecated and has no effect anymore/) do + builder_class.new(:foo, nil, nil, {}, proc {}) + end + end + protected - def protect_against_forgery? - false + + def hidden_fields(method = nil) + txt = %{<div style="margin:0;padding:0;display:inline">} + txt << %{<input name="utf8" type="hidden" value="✓" />} + if method && !method.to_s.in?(['get', 'post']) + txt << %{<input name="_method" type="hidden" value="#{method}" />} end + txt << %{</div>} + end + def form_text(action = "/", id = nil, html_class = nil, remote = nil, multipart = nil, method = nil) + txt = %{<form accept-charset="UTF-8" action="#{action}"} + txt << %{ enctype="multipart/form-data"} if multipart + txt << %{ data-remote="true"} if remote + txt << %{ class="#{html_class}"} if html_class + txt << %{ id="#{id}"} if id + method = method.to_s == "get" ? "get" : "post" + txt << %{ method="#{method}">} + end + + def whole_form(action = "/", id = nil, html_class = nil, options = nil) + contents = block_given? ? yield : "" + + if options.is_a?(Hash) + method, remote, multipart = options.values_at(:method, :remote, :multipart) + else + method = options + end + + form_text(action, id, html_class, remote, multipart, method) + hidden_fields(method) + contents + "</form>" + end + + def protect_against_forgery? + false + end end diff --git a/actionpack/test/template/form_options_helper_test.rb b/actionpack/test/template/form_options_helper_test.rb index a903e13bad..2c0da8473a 100644 --- a/actionpack/test/template/form_options_helper_test.rb +++ b/actionpack/test/template/form_options_helper_test.rb @@ -87,6 +87,20 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_collection_options_with_proc_for_value_method + assert_dom_equal( + "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>", + options_from_collection_for_select(dummy_posts, lambda { |p| p.author_name }, "title") + ) + end + + def test_collection_options_with_proc_for_text_method + assert_dom_equal( + "<option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option>", + options_from_collection_for_select(dummy_posts, "author_name", lambda { |p| p.title }) + ) + end + def test_string_options_for_select options = "<option value=\"Denmark\">Denmark</option><option value=\"USA\">USA</option><option value=\"Sweden\">Sweden</option>" assert_dom_equal( @@ -168,16 +182,16 @@ class FormOptionsHelperTest < ActionView::TestCase def test_hash_options_for_select assert_dom_equal( - "<option value=\"<Kroner>\"><DKR></option>\n<option value=\"Dollar\">$</option>", - options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").sort.join("\n") + "<option value=\"Dollar\">$</option>\n<option value=\"<Kroner>\"><DKR></option>", + options_for_select("$" => "Dollar", "<DKR>" => "<Kroner>").split("\n").join("\n") ) assert_dom_equal( - "<option value=\"<Kroner>\"><DKR></option>\n<option value=\"Dollar\" selected=\"selected\">$</option>", - options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").sort.join("\n") + "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"<Kroner>\"><DKR></option>", + options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, "Dollar").split("\n").join("\n") ) assert_dom_equal( - "<option value=\"<Kroner>\" selected=\"selected\"><DKR></option>\n<option value=\"Dollar\" selected=\"selected\">$</option>", - options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").sort.join("\n") + "<option value=\"Dollar\" selected=\"selected\">$</option>\n<option value=\"<Kroner>\" selected=\"selected\"><DKR></option>", + options_for_select({ "$" => "Dollar", "<DKR>" => "<Kroner>" }, [ "Dollar", "<Kroner>" ]).split("\n").join("\n") ) end @@ -495,7 +509,7 @@ class FormOptionsHelperTest < ActionView::TestCase def test_select_under_fields_for_with_string_and_given_prompt @post = Post.new - options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>" + options = "<option value=\"abe\">abe</option><option value=\"mus\">mus</option><option value=\"hest\">hest</option>".html_safe output_buffer = fields_for :post, @post do |f| concat f.select(:category, options, :prompt => 'The prompt') @@ -515,6 +529,14 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_with_multiple_and_without_hidden_input + output_buffer = select(:post, :category, "", {:include_hidden => false}, :multiple => true) + assert_dom_equal( + "<select multiple=\"multiple\" id=\"post_category\" name=\"post[category][]\"></select>", + output_buffer + ) + end + def test_select_with_multiple_and_disabled_to_add_disabled_hidden_input output_buffer = select(:post, :category, "", {}, :multiple => true, :disabled => true) assert_dom_equal( @@ -651,6 +673,13 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_select_escapes_options + assert_dom_equal( + '<select id="post_title" name="post[title]"><script>alert(1)</script></select>', + select('post', 'title', '<script>alert(1)</script>') + ) + end + def test_select_with_selected_nil @post = Post.new @post.category = "<mus>" @@ -790,7 +819,25 @@ class FormOptionsHelperTest < ActionView::TestCase assert_dom_equal( "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"<Abe>\"><Abe></option>\n<option value=\"Babe\" selected=\"selected\">Babe</option>\n<option value=\"Cabe\" disabled=\"disabled\">Cabe</option></select>", collection_select("post", "author_name", dummy_posts, "author_name", "author_name", :disabled => 'Cabe') - ) + ) + end + + def test_collection_select_with_proc_for_value_method + @post = Post.new + + assert_dom_equal( + "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>", + collection_select("post", "author_name", dummy_posts, lambda { |p| p.author_name }, "title") + ) + end + + def test_collection_select_with_proc_for_text_method + @post = Post.new + + assert_dom_equal( + "<select id=\"post_author_name\" name=\"post[author_name]\"><option value=\"<Abe>\"><Abe> went home</option>\n<option value=\"Babe\">Babe went home</option>\n<option value=\"Cabe\">Cabe went home</option></select>", + collection_select("post", "author_name", dummy_posts, "author_name", lambda { |p| p.title }) + ) end def test_time_zone_select @@ -1024,36 +1071,36 @@ class FormOptionsHelperTest < ActionView::TestCase end def test_option_html_attributes_from_without_hash - assert_dom_equal( - "", + assert_equal( + {}, option_html_attributes([ 'foo', 'bar' ]) ) end def test_option_html_attributes_with_single_element_hash - assert_dom_equal( - " class=\"fancy\"", + assert_equal( + {:class => 'fancy'}, option_html_attributes([ 'foo', 'bar', { :class => 'fancy' } ]) ) end def test_option_html_attributes_with_multiple_element_hash - assert_dom_equal( - " class=\"fancy\" onclick=\"alert('Hello World');\"", + assert_equal( + {:class => 'fancy', 'onclick' => "alert('Hello World');"}, option_html_attributes([ 'foo', 'bar', { :class => 'fancy', 'onclick' => "alert('Hello World');" } ]) ) end def test_option_html_attributes_with_multiple_hashes - assert_dom_equal( - " class=\"fancy\" onclick=\"alert('Hello World');\"", + assert_equal( + {:class => 'fancy', 'onclick' => "alert('Hello World');"}, option_html_attributes([ 'foo', 'bar', { :class => 'fancy' }, { 'onclick' => "alert('Hello World');" } ]) ) end def test_option_html_attributes_with_special_characters - assert_dom_equal( - " onclick=\"alert("<code>")\"", + assert_equal( + {:onclick => "alert("<code>")"}, option_html_attributes([ 'foo', 'bar', { :onclick => %(alert("<code>")) } ]) ) end @@ -1068,6 +1115,24 @@ class FormOptionsHelperTest < ActionView::TestCase ) end + def test_grouped_collection_select_with_selected + @post = Post.new + + assert_dom_equal( + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option value="dk" selected="selected">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, + grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :selected => 'dk') + ) + end + + def test_grouped_collection_select_with_disabled_value + @post = Post.new + + assert_dom_equal( + %Q{<select id="post_origin" name="post[origin]"><optgroup label="<Africa>"><option value="<sa>"><South Africa></option>\n<option value="so">Somalia</option></optgroup><optgroup label="Europe"><option disabled="disabled" value="dk">Denmark</option>\n<option value="ie">Ireland</option></optgroup></select>}, + grouped_collection_select("post", "origin", dummy_continents, :countries, :continent_name, :country_id, :country_name, :disabled => 'dk') + ) + end + def test_grouped_collection_select_under_fields_for @post = Post.new @post.origin = 'dk' @@ -1084,14 +1149,14 @@ class FormOptionsHelperTest < ActionView::TestCase private - def dummy_posts - [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"), - Post.new("Babe went home", "Babe", "To a little house", "shh!"), - Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ] - end + def dummy_posts + [ Post.new("<Abe> went home", "<Abe>", "To a little house", "shh!"), + Post.new("Babe went home", "Babe", "To a little house", "shh!"), + Post.new("Cabe went home", "Cabe", "To a little house", "shh!") ] + end - def dummy_continents - [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")] ), - Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")] ) ] - end + def dummy_continents + [ Continent.new("<Africa>", [Country.new("<sa>", "<South Africa>"), Country.new("so", "Somalia")]), + Continent.new("Europe", [Country.new("dk", "Denmark"), Country.new("ie", "Ireland")]) ] + end end diff --git a/actionpack/test/template/form_tag_helper_test.rb b/actionpack/test/template/form_tag_helper_test.rb index 2f2546aed2..1e92ff99ff 100644 --- a/actionpack/test/template/form_tag_helper_test.rb +++ b/actionpack/test/template/form_tag_helper_test.rb @@ -78,6 +78,12 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal expected, actual end + def test_form_tag_with_method_patch + actual = form_tag({}, { :method => :patch }) + expected = whole_form("http://www.example.com", :method => :patch) + assert_dom_equal expected, actual + end + def test_form_tag_with_method_put actual = form_tag({}, { :method => :put }) expected = whole_form("http://www.example.com", :method => :put) @@ -216,19 +222,19 @@ class FormTagHelperTest < ActionView::TestCase def test_text_area_tag_size_string actual = text_area_tag "body", "hello world", "size" => "20x40" - expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>) + expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>) assert_dom_equal expected, actual end def test_text_area_tag_size_symbol actual = text_area_tag "body", "hello world", :size => "20x40" - expected = %(<textarea cols="20" id="body" name="body" rows="40">hello world</textarea>) + expected = %(<textarea cols="20" id="body" name="body" rows="40">\nhello world</textarea>) assert_dom_equal expected, actual end def test_text_area_tag_should_disregard_size_if_its_given_as_an_integer actual = text_area_tag "body", "hello world", :size => 20 - expected = %(<textarea id="body" name="body">hello world</textarea>) + expected = %(<textarea id="body" name="body">\nhello world</textarea>) assert_dom_equal expected, actual end @@ -239,19 +245,19 @@ class FormTagHelperTest < ActionView::TestCase def test_text_area_tag_escape_content actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40" - expected = %(<textarea cols="20" id="body" name="body" rows="40"><b>hello world</b></textarea>) + expected = %(<textarea cols="20" id="body" name="body" rows="40">\n<b>hello world</b></textarea>) assert_dom_equal expected, actual end def test_text_area_tag_unescaped_content actual = text_area_tag "body", "<b>hello world</b>", :size => "20x40", :escape => false - expected = %(<textarea cols="20" id="body" name="body" rows="40"><b>hello world</b></textarea>) + expected = %(<textarea cols="20" id="body" name="body" rows="40">\n<b>hello world</b></textarea>) assert_dom_equal expected, actual end def test_text_area_tag_unescaped_nil_content actual = text_area_tag "body", nil, :escape => false - expected = %(<textarea id="body" name="body"></textarea>) + expected = %(<textarea id="body" name="body">\n</textarea>) assert_dom_equal expected, actual end @@ -457,11 +463,16 @@ class FormTagHelperTest < ActionView::TestCase assert_dom_equal(expected, search_field_tag("query")) end - def telephone_field_tag + def test_telephone_field_tag expected = %{<input id="cell" name="cell" type="tel" />} assert_dom_equal(expected, telephone_field_tag("cell")) end + def test_date_field_tag + expected = %{<input id="cell" name="cell" type="date" />} + assert_dom_equal(expected, date_field_tag("cell")) + end + def test_url_field_tag expected = %{<input id="homepage" name="homepage" type="url" />} assert_dom_equal(expected, url_field_tag("homepage")) @@ -502,6 +513,16 @@ class FormTagHelperTest < ActionView::TestCase expected = %(<fieldset class="format">Hello world!</fieldset>) assert_dom_equal expected, output_buffer + + output_buffer = render_erb("<%= field_set_tag %>") + + expected = %(<fieldset></fieldset>) + assert_dom_equal expected, output_buffer + + output_buffer = render_erb("<%= field_set_tag('You legend!') %>") + + expected = %(<fieldset><legend>You legend!</legend></fieldset>) + assert_dom_equal expected, output_buffer end def test_text_area_tag_options_symbolize_keys_side_effects diff --git a/actionpack/test/template/html-scanner/sanitizer_test.rb b/actionpack/test/template/html-scanner/sanitizer_test.rb index 32c655c5fd..324caef224 100644 --- a/actionpack/test/template/html-scanner/sanitizer_test.rb +++ b/actionpack/test/template/html-scanner/sanitizer_test.rb @@ -125,6 +125,24 @@ class SanitizerTest < ActionController::TestCase assert_equal(text, sanitizer.sanitize(text, :attributes => ['foo'])) end + def test_should_raise_argument_error_if_tags_is_not_enumerable + sanitizer = HTML::WhiteListSanitizer.new + e = assert_raise(ArgumentError) do + sanitizer.sanitize('', :tags => 'foo') + end + + assert_equal "You should pass :tags as an Enumerable", e.message + end + + def test_should_raise_argument_error_if_attributes_is_not_enumerable + sanitizer = HTML::WhiteListSanitizer.new + e = assert_raise(ArgumentError) do + sanitizer.sanitize('', :attributes => 'foo') + end + + assert_equal "You should pass :attributes as an Enumerable", e.message + end + [%w(img src), %w(a href)].each do |(tag, attr)| define_method "test_should_strip_#{attr}_attribute_in_#{tag}_with_bad_protocols" do assert_sanitized %(<#{tag} #{attr}="javascript:bang" title="1">boo</#{tag}>), %(<#{tag} title="1">boo</#{tag}>) diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index d98ffe8fa7..64898f7ad1 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'active_support/core_ext/string/encoding' class JavaScriptHelperTest < ActionView::TestCase tests ActionView::Helpers::JavaScriptHelper @@ -29,6 +28,8 @@ class JavaScriptHelperTest < ActionView::TestCase assert_equal %(backslash\\\\test), escape_javascript( %(backslash\\test) ) assert_equal %(dont <\\/close> tags), escape_javascript(%(dont </close> tags)) assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\250 newline).force_encoding('UTF-8').encode!) + assert_equal %(unicode 
 newline), escape_javascript(%(unicode \342\200\251 newline).force_encoding('UTF-8').encode!) + assert_equal %(dont <\\/close> tags), j(%(dont </close> tags)) end diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index c65f707da0..96b14a0acd 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -78,9 +78,9 @@ class LookupContextTest < ActiveSupport::TestCase end test "found templates respects given formats if one cannot be found from template or handler" do - ActionView::Template::Handlers::ERB.expects(:default_format).returns(nil) + ActionView::Template::Handlers::Builder.expects(:default_format).returns(nil) @lookup_context.formats = [:text] - template = @lookup_context.find("hello_world", %w(test)) + template = @lookup_context.find("hello", %w(test)) assert_equal [:text], template.formats end diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index 482d953907..5c6f23d70b 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -96,6 +96,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("0.001", number_with_precision(0.00111, :precision => 3)) assert_equal("10.00", number_with_precision(9.995, :precision => 2)) assert_equal("11.00", number_with_precision(10.995, :precision => 2)) + assert_equal("0.00", number_with_precision(-0.001, :precision => 2)) end def test_number_with_precision_with_custom_delimiter_and_separator diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index 5d3dc73ed2..122b07d348 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -51,6 +51,18 @@ module RenderTestCases assert_match "<error>No Comment</error>", @view.render(:template => "comments/empty", :formats => [:xml]) end + def test_render_partial_implicitly_use_format_of_the_rendered_template + @view.lookup_context.formats = [:json] + assert_equal "Hello world", @view.render(:template => "test/one", :formats => [:html]) + end + + def test_render_template_with_a_missing_partial_of_another_format + @view.lookup_context.formats = [:html] + assert_raise ActionView::Template::Error, "Missing partial /missing with {:locale=>[:en], :formats=>[:json], :handlers=>[:erb, :builder]}" do + @view.render(:template => "with_format", :formats => [:json]) + end + end + def test_render_file_with_locale assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => [:de]) assert_equal "<h1>Kein Kommentar</h1>", @view.render(:file => "comments/empty", :locale => :de) @@ -226,6 +238,10 @@ module RenderTestCases def test_render_partial_with_nil_values_in_collection assert_equal "Hello: davidHello: Anonymous", @view.render(:partial => "test/customer", :collection => [ Customer.new("david"), nil ]) end + + def test_render_partial_with_layout_using_collection_and_template + assert_equal "<b>Hello: Amazon</b><b>Hello: Yahoo</b>", @view.render(:partial => "test/customer", :layout => 'test/b_layout_for_partial', :collection => [ Customer.new("Amazon"), Customer.new("Yahoo") ]) + end def test_render_partial_with_empty_array_should_return_nil assert_nil @view.render(:partial => []) @@ -299,6 +315,12 @@ module RenderTestCases ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, <%= name %>!"', @view.render(:inline => "Hello, <%= name %>!", :locals => { :name => "Josh" }, :type => :foo) end + + def test_render_knows_about_types_registered_when_extensions_are_checked_earlier_in_initialization + ActionView::Template::Handlers.extensions + ActionView::Template.register_template_handler :foo, CustomHandler + assert ActionView::Template::Handlers.extensions.include?(:foo) + end def test_render_ignores_templates_with_malformed_template_handlers ActiveSupport::Deprecation.silence do diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb deleted file mode 100644 index 1c591bdcc2..0000000000 --- a/actionpack/test/template/sprockets_helper_test.rb +++ /dev/null @@ -1,348 +0,0 @@ -require 'abstract_unit' -require 'sprockets' -require 'sprockets/helpers/rails_helper' -require 'mocha' - -class SprocketsHelperTest < ActionView::TestCase - include Sprockets::Helpers::RailsHelper - - attr_accessor :assets - - class MockRequest - def protocol() 'http://' end - def ssl?() false end - def host_with_port() 'localhost' end - end - - def setup - super - - @controller = BasicController.new - @controller.request = MockRequest.new - - @assets = Sprockets::Environment.new - @assets.append_path(FIXTURES.join("sprockets/app/javascripts")) - @assets.append_path(FIXTURES.join("sprockets/app/stylesheets")) - @assets.append_path(FIXTURES.join("sprockets/app/images")) - @assets.append_path(FIXTURES.join("sprockets/app/fonts")) - - application = Struct.new(:config, :assets).new(config, @assets) - Rails.stubs(:application).returns(application) - @config = config - @config.perform_caching = true - @config.assets.digest = true - @config.assets.compile = true - end - - def url_for(*args) - "http://www.example.com" - end - - def config - @controller ? @controller.config : @config - end - - def compute_host(source, request, options = {}) - raise "Should never get here" - end - - test "asset_path" do - assert_match %r{/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - assert_match %r{/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png", :digest => true) - assert_match %r{/assets/logo.png}, - asset_path("logo.png", :digest => false) - end - - test "custom_asset_path" do - @config.assets.prefix = '/s' - assert_match %r{/s/logo-[0-9a-f]+.png}, - asset_path("logo.png") - assert_match %r{/s/logo-[0-9a-f]+.png}, - asset_path("logo.png", :digest => true) - assert_match %r{/s/logo.png}, - asset_path("logo.png", :digest => false) - end - - test "asset_path with root relative assets" do - assert_equal "/images/logo", - asset_path("/images/logo") - assert_equal "/images/logo.gif", - asset_path("/images/logo.gif") - - assert_equal "/dir/audio", - asset_path("/dir/audio") - end - - test "asset_path with absolute urls" do - assert_equal "http://www.example.com/video/play", - asset_path("http://www.example.com/video/play") - assert_equal "http://www.example.com/video/play.mp4", - asset_path("http://www.example.com/video/play.mp4") - end - - test "with a simple asset host the url should default to protocol relative" do - @controller.config.default_asset_host_protocol = :relative - @controller.config.asset_host = "assets-%d.example.com" - assert_match %r{^//assets-\d.example.com/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "with a simple asset host the url can be changed to use the request protocol" do - @controller.config.asset_host = "assets-%d.example.com" - @controller.config.default_asset_host_protocol = :request - assert_match %r{http://assets-\d.example.com/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "With a proc asset host that returns no protocol the url should be protocol relative" do - @controller.config.default_asset_host_protocol = :relative - @controller.config.asset_host = Proc.new do |asset| - "assets-999.example.com" - end - assert_match %r{^//assets-999.example.com/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "with a proc asset host that returns a protocol the url use it" do - @controller.config.asset_host = Proc.new do |asset| - "http://assets-999.example.com" - end - assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "stylesheets served with a controller in scope can access the request" do - config.asset_host = Proc.new do |asset, request| - assert_not_nil request - "http://assets-666.example.com" - end - assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "stylesheets served without a controller in scope cannot access the request" do - @controller = nil - @config.asset_host = Proc.new do |asset, request| - fail "This should not have been called." - end - assert_raises ActionController::RoutingError do - asset_path("logo.png") - end - @config.asset_host = method :compute_host - assert_raises ActionController::RoutingError do - asset_path("logo.png") - end - end - - test "image_tag" do - assert_dom_equal '<img alt="Xml" src="/assets/xml.png" />', image_tag("xml.png") - end - - test "image_path" do - assert_match %r{/assets/logo-[0-9a-f]+.png}, - image_path("logo.png") - - assert_match %r{/assets/logo-[0-9a-f]+.png}, - path_to_image("logo.png") - end - - test "font_path" do - assert_match %r{/assets/font-[0-9a-f]+.ttf}, - font_path("font.ttf") - - assert_match %r{/assets/font-[0-9a-f]+.ttf}, - path_to_font("font.ttf") - end - - test "javascript_path" do - assert_match %r{/assets/application-[0-9a-f]+.js}, - javascript_path("application") - - assert_match %r{/assets/application-[0-9a-f]+.js}, - javascript_path("application.js") - - assert_match %r{/assets/application-[0-9a-f]+.js}, - path_to_javascript("application.js") - end - - test "stylesheet_path" do - assert_match %r{/assets/application-[0-9a-f]+.css}, - stylesheet_path("application") - - assert_match %r{/assets/application-[0-9a-f]+.css}, - stylesheet_path("application.css") - - assert_match %r{/assets/application-[0-9a-f]+.css}, - path_to_stylesheet("application.css") - end - - test "stylesheets served without a controller in do not use asset hosts when the default protocol is :request" do - @controller = nil - @config.asset_host = "assets-%d.example.com" - @config.default_asset_host_protocol = :request - @config.perform_caching = true - - assert_match %r{/assets/logo-[0-9a-f]+.png}, - asset_path("logo.png") - end - - test "asset path with relative url root" do - @controller.config.relative_url_root = "/collaboration/hieraki" - assert_equal "/collaboration/hieraki/images/logo.gif", - asset_path("/images/logo.gif") - end - - test "asset path with relative url root when controller isn't present but relative_url_root is" do - @controller = nil - @config.relative_url_root = "/collaboration/hieraki" - assert_equal "/collaboration/hieraki/images/logo.gif", - asset_path("/images/logo.gif") - end - - test "font path through asset_path" do - assert_match %r{/assets/font-[0-9a-f]+.ttf}, - asset_path('font.ttf') - - assert_match %r{/assets/dir/font-[0-9a-f]+.ttf}, - asset_path("dir/font.ttf") - - assert_equal "http://www.example.com/fonts/font.ttf", - asset_path("http://www.example.com/fonts/font.ttf") - end - - test "javascript path through asset_path" do - assert_match %r{/assets/application-[0-9a-f]+.js}, - asset_path(:application, :ext => "js") - - assert_match %r{/assets/xmlhr-[0-9a-f]+.js}, - asset_path("xmlhr", :ext => "js") - assert_match %r{/assets/dir/xmlhr-[0-9a-f]+.js}, - asset_path("dir/xmlhr.js", :ext => "js") - - assert_equal "/dir/xmlhr.js", - asset_path("/dir/xmlhr", :ext => "js") - - assert_equal "http://www.example.com/js/xmlhr", - asset_path("http://www.example.com/js/xmlhr", :ext => "js") - assert_equal "http://www.example.com/js/xmlhr.js", - asset_path("http://www.example.com/js/xmlhr.js", :ext => "js") - end - - test "javascript include tag" do - assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>}, - javascript_include_tag(:application) - assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>}, - javascript_include_tag(:application, :digest => true) - assert_match %r{<script src="/assets/application.js" type="text/javascript"></script>}, - javascript_include_tag(:application, :digest => false) - - assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>}, - javascript_include_tag("xmlhr") - assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js" type="text/javascript"></script>}, - javascript_include_tag("xmlhr.js") - assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>', - javascript_include_tag("http://www.example.com/xmlhr") - - assert_match %r{<script src=\"/assets/xmlhr-[0-9a-f]+.js" type=\"text/javascript\"></script>\n<script src=\"/assets/extra-[0-9a-f]+.js" type=\"text/javascript\"></script>}, - javascript_include_tag("xmlhr", "extra") - - assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>}, - javascript_include_tag(:application, :debug => true) - - assert_match %r{<script src="/assets/jquery.plugin.js" type="text/javascript"></script>}, - javascript_include_tag('jquery.plugin', :digest => false) - - @config.assets.compile = true - @config.assets.debug = true - assert_match %r{<script src="/javascripts/application.js" type="text/javascript"></script>}, - javascript_include_tag('/javascripts/application') - assert_match %r{<script src="/assets/xmlhr-[0-9a-f]+.js\?body=1" type="text/javascript"></script>\n<script src="/assets/application-[0-9a-f]+.js\?body=1" type="text/javascript"></script>}, - javascript_include_tag(:application) - end - - test "stylesheet path through asset_path" do - assert_match %r{/assets/application-[0-9a-f]+.css}, asset_path(:application, :ext => "css") - - assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css") - assert_match %r{/assets/dir/style-[0-9a-f]+.css}, asset_path("dir/style.css", :ext => "css") - assert_equal "/dir/style.css", asset_path("/dir/style.css", :ext => "css") - - assert_equal "http://www.example.com/css/style", - asset_path("http://www.example.com/css/style", :ext => "css") - assert_equal "http://www.example.com/css/style.css", - asset_path("http://www.example.com/css/style.css", :ext => "css") - end - - test "stylesheet link tag" do - assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application) - assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application, :digest => true) - assert_match %r{<link href="/assets/application.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application, :digest => false) - - assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag("style") - assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag("style.css") - - assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />', - stylesheet_link_tag("http://www.example.com/style.css") - assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="all" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag("style", :media => "all") - assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="print" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag("style", :media => "print") - - assert_match %r{<link href="/assets/style-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/extra-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag("style", "extra") - - assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application, :debug => true) - - @config.assets.compile = true - @config.assets.debug = true - assert_match %r{<link href="/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag('/stylesheets/application') - - assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application) - - assert_match %r{<link href="/assets/style-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />\n<link href="/assets/application-[0-9a-f]+.css\?body=1" media="print" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application, :media => "print") - end - - test "alternate asset prefix" do - stubs(:asset_prefix).returns("/themes/test") - assert_match %r{/themes/test/style-[0-9a-f]+.css}, asset_path("style", :ext => "css") - end - - test "alternate asset environment" do - assets = Sprockets::Environment.new - assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets")) - stubs(:asset_environment).returns(assets) - assert_match %r{/assets/style-[0-9a-f]+.css}, asset_path("style", :ext => "css") - end - - test "alternate hash based on environment" do - assets = Sprockets::Environment.new - assets.version = 'development' - assets.append_path(FIXTURES.join("sprockets/alternate/stylesheets")) - stubs(:asset_environment).returns(assets) - dev_path = asset_path("style", :ext => "css") - - assets.version = 'production' - prod_path = asset_path("style", :ext => "css") - - assert_not_equal prod_path, dev_path - end - - test "precedence of `config.digest = false` over manifest.yml asset digests" do - Rails.application.config.assets.digests = {'logo.png' => 'logo-d1g3st.png'} - @config.assets.digest = false - - assert_equal '/assets/logo.png', - asset_path("logo.png") - end -end diff --git a/actionpack/test/template/sprockets_helper_with_routes_test.rb b/actionpack/test/template/sprockets_helper_with_routes_test.rb deleted file mode 100644 index bcbd81a7dd..0000000000 --- a/actionpack/test/template/sprockets_helper_with_routes_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'abstract_unit' -require 'sprockets' -require 'sprockets/helpers/rails_helper' -require 'mocha' - -class SprocketsHelperWithRoutesTest < ActionView::TestCase - include Sprockets::Helpers::RailsHelper - - # Let's bring in some named routes to test namespace conflicts with potential *_paths. - # We have to do this after we bring in the Sprockets RailsHelper so if there are conflicts, - # they'll fail in the way we expect in a real live Rails app. - routes = ActionDispatch::Routing::RouteSet.new - routes.draw do - resources :assets - end - include routes.url_helpers - - def setup - super - @controller = BasicController.new - - @assets = Sprockets::Environment.new - @assets.append_path(FIXTURES.join("sprockets/app/javascripts")) - @assets.append_path(FIXTURES.join("sprockets/app/stylesheets")) - @assets.append_path(FIXTURES.join("sprockets/app/images")) - - application = Struct.new(:config, :assets).new(config, @assets) - Rails.stubs(:application).returns(application) - @config = config - @config.perform_caching = true - @config.assets.digest = true - @config.assets.compile = true - end - - test "namespace conflicts on a named route called asset_path" do - # Testing this for sanity - asset_path is now a named route! - assert_match asset_path('test_asset'), '/assets/test_asset' - - assert_match %r{/assets/logo-[0-9a-f]+.png}, - path_to_asset("logo.png") - assert_match %r{/assets/logo-[0-9a-f]+.png}, - path_to_asset("logo.png", :digest => true) - assert_match %r{/assets/logo.png}, - path_to_asset("logo.png", :digest => false) - end - - test "javascript_include_tag with a named_route named asset_path" do - assert_match %r{<script src="/assets/application-[0-9a-f]+.js" type="text/javascript"></script>}, - javascript_include_tag(:application) - end - - test "stylesheet_link_tag with a named_route named asset_path" do - assert_match %r{<link href="/assets/application-[0-9a-f]+.css" media="screen" rel="stylesheet" type="text/css" />}, - stylesheet_link_tag(:application) - end - -end
\ No newline at end of file diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 3084527f70..8c57ada587 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -11,6 +11,8 @@ class TestERBTemplate < ActiveSupport::TestCase def find_template(*args) end + + attr_accessor :formats end class Context diff --git a/actionpack/test/template/test_case_test.rb b/actionpack/test/template/test_case_test.rb index 37858c1ba2..f2ed2ec609 100644 --- a/actionpack/test/template/test_case_test.rb +++ b/actionpack/test/template/test_case_test.rb @@ -277,6 +277,12 @@ module ActionView end class RenderTemplateTest < ActionView::TestCase + test "supports specifying templates with a Regexp" do + controller.controller_path = "fun" + render(:template => "fun/games/hello_world") + assert_template %r{\Afun/games/hello_world\Z} + end + test "supports specifying partials" do controller.controller_path = "test" render(:template => "test/calling_partial_with_layout") diff --git a/actionpack/test/template/text_helper_test.rb b/actionpack/test/template/text_helper_test.rb index aa185d9cb0..5865b7f23c 100644 --- a/actionpack/test/template/text_helper_test.rb +++ b/actionpack/test/template/text_helper_test.rb @@ -91,12 +91,12 @@ class TextHelperTest < ActionView::TestCase def test_highlight assert_equal( - "This is a <strong class=\"highlight\">beautiful</strong> morning", + "This is a <mark>beautiful</mark> morning", highlight("This is a beautiful morning", "beautiful") ) assert_equal( - "This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day", + "This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day", highlight("This is a beautiful morning, but also a beautiful day", "beautiful") ) @@ -115,31 +115,31 @@ class TextHelperTest < ActionView::TestCase def test_highlight_should_sanitize_input assert_equal( - "This is a <strong class=\"highlight\">beautiful</strong> morning", + "This is a <mark>beautiful</mark> morning", highlight("This is a beautiful morning<script>code!</script>", "beautiful") ) end def test_highlight_should_not_sanitize_if_sanitize_option_if_false assert_equal( - "This is a <strong class=\"highlight\">beautiful</strong> morning<script>code!</script>", + "This is a <mark>beautiful</mark> morning<script>code!</script>", highlight("This is a beautiful morning<script>code!</script>", "beautiful", :sanitize => false) ) end def test_highlight_with_regexp assert_equal( - "This is a <strong class=\"highlight\">beautiful!</strong> morning", + "This is a <mark>beautiful!</mark> morning", highlight("This is a beautiful! morning", "beautiful!") ) assert_equal( - "This is a <strong class=\"highlight\">beautiful! morning</strong>", + "This is a <mark>beautiful! morning</mark>", highlight("This is a beautiful! morning", "beautiful! morning") ) assert_equal( - "This is a <strong class=\"highlight\">beautiful? morning</strong>", + "This is a <mark>beautiful? morning</mark>", highlight("This is a beautiful? morning", "beautiful? morning") ) end @@ -157,23 +157,23 @@ class TextHelperTest < ActionView::TestCase def test_highlight_with_html assert_equal( - "<p>This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>", + "<p>This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>", highlight("<p>This is a beautiful morning, but also a beautiful day</p>", "beautiful") ) assert_equal( - "<p>This is a <em><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>", + "<p>This is a <em><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> day</p>", highlight("<p>This is a <em>beautiful</em> morning, but also a beautiful day</p>", "beautiful") ) assert_equal( - "<p>This is a <em class=\"error\"><strong class=\"highlight\">beautiful</strong></em> morning, but also a <strong class=\"highlight\">beautiful</strong> <span class=\"last\">day</span></p>", + "<p>This is a <em class=\"error\"><mark>beautiful</mark></em> morning, but also a <mark>beautiful</mark> <span class=\"last\">day</span></p>", highlight("<p>This is a <em class=\"error\">beautiful</em> morning, but also a beautiful <span class=\"last\">day</span></p>", "beautiful") ) assert_equal( - "<p class=\"beautiful\">This is a <strong class=\"highlight\">beautiful</strong> morning, but also a <strong class=\"highlight\">beautiful</strong> day</p>", + "<p class=\"beautiful\">This is a <mark>beautiful</mark> morning, but also a <mark>beautiful</mark> day</p>", highlight("<p class=\"beautiful\">This is a beautiful morning, but also a beautiful day</p>", "beautiful") ) assert_equal( - "<p>This is a <strong class=\"highlight\">beautiful</strong> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <strong class=\"highlight\">beautiful</strong> day</p>", + "<p>This is a <mark>beautiful</mark> <a href=\"http://example.com/beautiful#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a <mark>beautiful</mark> day</p>", highlight("<p>This is a beautiful <a href=\"http://example.com/beautiful\#top?what=beautiful%20morning&when=now+then\">morning</a>, but also a beautiful day</p>", "beautiful") ) assert_equal( diff --git a/actionpack/test/template/url_helper_test.rb b/actionpack/test/template/url_helper_test.rb index 37ec0e323d..b482bd3251 100644 --- a/actionpack/test/template/url_helper_test.rb +++ b/actionpack/test/template/url_helper_test.rb @@ -1,6 +1,5 @@ # encoding: utf-8 require 'abstract_unit' -require 'active_support/ordered_hash' require 'controller/fake_controllers' class UrlHelperTest < ActiveSupport::TestCase diff --git a/actionpack/test/ts_isolated.rb b/actionpack/test/ts_isolated.rb index 7430de2299..ae2a0c95f6 100644 --- a/actionpack/test/ts_isolated.rb +++ b/actionpack/test/ts_isolated.rb @@ -3,7 +3,6 @@ $:.unshift(File.dirname(__FILE__) + '/../../activesupport/lib') require 'minitest/autorun' require 'rbconfig' -require 'active_support/core_ext/kernel/reporting' require 'abstract_unit' class TestIsolated < ActiveSupport::TestCase |