diff options
Diffstat (limited to 'actionpack')
41 files changed, 917 insertions, 268 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index b57408ede3..c3df2ebc0c 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,9 +1,57 @@ ## Rails 4.0.0 (unreleased) ## -* Add :if / :unless conditions to fragment cache: - <%= cache @model, if: some_condition(@model) do %> +* Clear url helper methods when routes are reloaded. *Andrew White* - *Stephen Ausman + Fabrizio Regini* +* Fix a bug in `ActionDispatch::Request#raw_post` that caused `env['rack.input']` + to be read but not rewound. + + *Matt Venables* + +* Prevent raising EOFError on multipart GET request (IE issue). *Adam Stankiewicz* + +* Rename all action callbacks from *_filter to *_action to avoid the misconception that these + callbacks are only suited for transforming or halting the response. With the new style, + it's more inviting to use them as they were intended, like setting shared ivars for views. + + Example: + + class PeopleController < ActionController::Base + before_action :set_person, except: [:index, :new, :create] + before_action :ensure_permission, only: [:edit, :update] + + ... + + private + def set_person + @person = current_account.people.find(params[:id]) + end + + def ensure_permission + current_person.can_change?(@person) + end + end + + The old *_filter methods still work with no deprecation notice. + + *DHH* + +* Add `cache_if` and `cache_unless` for conditional fragment caching: + + Example: + + <%= cache_if condition, project do %> + <b>All the topics on this project</b> + <%= render project.topics %> + <% end %> + + # and + + <%= cache_unless condition, project do %> + <b>All the topics on this project</b> + <%= render project.topics %> + <% end %> + + *Stephen Ausman + Fabrizio Regini + Angelo Capilleri* * Add filter capability to ActionController logs for redirect locations: @@ -20,7 +68,7 @@ an invalid `:layout` argument. #8376 - render :partial => 'partial', :layout => true + render partial: 'partial', layout: true # results in ActionView::MissingTemplate: Missing partial /true @@ -38,11 +86,11 @@ *Drew Ulmer* -* No sort Hash options in #grouped_options_for_select. *Sergey Kojin* +* No sort Hash options in `grouped_options_for_select`. *Sergey Kojin* -* Accept symbols as #send_data :disposition value *Elia Schito* +* Accept symbols as `send_data :disposition` value *Elia Schito* -* Add i18n scope to distance_of_time_in_words. *Steve Klabnik* +* Add i18n scope to `distance_of_time_in_words`. *Steve Klabnik* * `assert_template`: - is no more passing with empty string. @@ -97,26 +145,22 @@ *Joost Baaij* -* Fix input name when `:multiple => true` and `:index` are set. +* Fix input name when `multiple: true` and `:index` are set. Before: - check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" /> After: - check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1) + check_box("post", "comment_ids", { multiple: true, index: "foo" }, 1) #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" /> Fix #8108 *Daniel Fox, Grant Hutchins & Trace Wax* -* Clear url helpers when reloading routes. - - *Santiago Pastorino* - * `BestStandardsSupport` middleware now appends it's `X-UA-Compatible` value to app's returned value if any. Fix #8086 @@ -282,7 +326,7 @@ New applications are generated with: - protect_from_forgery :with => :exception + protect_from_forgery with: :exception *Sergey Nartimov* @@ -290,7 +334,7 @@ * Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`: - excerpt('This is a very beautiful morning', 'very', :separator => ' ', :radius => 1) + excerpt('This is a very beautiful morning', 'very', separator: ' ', radius: 1) # => ...a very beautiful... *Guirec Corbel* @@ -407,7 +451,7 @@ We recommend the use of Unobtrusive JavaScript instead. For example: - link_to "Greeting", "#", :class => "nav_link" + link_to "Greeting", "#", class: "nav_link" $(function() { $('.nav_link').click(function() { @@ -453,7 +497,7 @@ * Remove `ActionDispatch::Head` middleware in favor of `Rack::Head`. *Santiago Pastorino* -* Deprecate `:confirm` in favor of `:data => { :confirm => "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers. +* Deprecate `:confirm` in favor of `data: { confirm: "Text" }` option for `button_to`, `button_tag`, `image_submit_tag`, `link_to` and `submit_tag` helpers. *Carlos Galdino + Rafael Mendonça França* @@ -465,7 +509,7 @@ add_flash_types :error, :warning end - If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, :error => 'message'` in a controller. + If you add the above code, you can use `<%= error %>` in an erb, and `redirect_to /foo, error: 'message'` in a controller. *kennyj* @@ -548,7 +592,7 @@ * Templates without a handler extension now raises a deprecation warning but still defaults to ERb. In future releases, it will simply return the template contents. *Steve Klabnik* -* Deprecate `:disable_with` in favor of `:data => { :disable_with => "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers. +* Deprecate `:disable_with` in favor of `data: { disable_with: "Text" }` option from `submit_tag`, `button_tag` and `button_to` helpers. *Carlos Galdino + Rafael Mendonça França* @@ -570,7 +614,7 @@ * Add backtrace to development routing error page. *Richard Schneeman* -* Replace `include_seconds` boolean argument with `:include_seconds => true` option +* Replace `include_seconds` boolean argument with `include_seconds: true` option in `distance_of_time_in_words` and `time_ago_in_words` signature. *Dmitriy Kiriyenko* * Make current object and counter (when it applies) variables accessible when @@ -594,11 +638,11 @@ * Changed default value for `config.action_view.embed_authenticity_token_in_remote_forms` to `false`. This change breaks remote forms that need to work also without javascript, so if you need such behavior, you can either set it to `true` or explicitly pass - `:authenticity_token => true` in form options + `authenticity_token: true` in form options * Added ActionDispatch::SSL middleware that when included force all the requests to be under HTTPS protocol. *Rafael Mendonça França* -* 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* +* 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* @@ -615,7 +659,7 @@ * 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? + force_ssl if: :ssl_configured? def ssl_configured? !Rails.env.development? @@ -684,15 +728,15 @@ *Carlos Antonio da Silva + Rafael Mendonça França* -* check_box with `:form` html5 attribute will now replicate the `:form` +* `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* +* `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 `:format` option to `number_to_percentage`. *Rodrigo Flores* * Add `config.action_view.logger` to configure logger for Action View. *Rafael Mendonça França* @@ -712,7 +756,7 @@ * Deprecated `ActionController::Routing` in favour of `ActionDispatch::Routing`. -* `check_box helper` with `:disabled => true` will generate a disabled +* `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* diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 02ac111392..599fff81c2 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -40,19 +40,22 @@ module AbstractController end end - # Skip before, after, and around filters matching any of the names + # Skip before, after, and around action callbacks matching any of the names + # Aliased as skip_filter. # # ==== Parameters # * <tt>names</tt> - A list of valid names that could be used for # 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) - skip_before_filter(*names) - skip_after_filter(*names) - skip_around_filter(*names) + def skip_action_callback(*names) + skip_before_action(*names) + skip_after_action(*names) + skip_around_action(*names) end + alias_method :skip_filter, :skip_action_callback + # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract # the normalization across several methods that use it. @@ -75,119 +78,138 @@ module AbstractController end ## - # :method: before_filter + # :method: before_action # - # :call-seq: before_filter(names, block) + # :call-seq: before_action(names, block) # - # Append a before filter. See _insert_callbacks for parameter details. + # Append a callback before actions. See _insert_callbacks for parameter details. + # Aliased as before_filter. ## - # :method: prepend_before_filter + # :method: prepend_before_action # - # :call-seq: prepend_before_filter(names, block) + # :call-seq: prepend_before_action(names, block) # - # Prepend a before filter. See _insert_callbacks for parameter details. + # Prepend a callback before actions. See _insert_callbacks for parameter details. + # Aliased as prepend_before_filter. ## - # :method: skip_before_filter + # :method: skip_before_action # - # :call-seq: skip_before_filter(names) + # :call-seq: skip_before_action(names) # - # Skip a before filter. See _insert_callbacks for parameter details. + # Skip a callback before actions. See _insert_callbacks for parameter details. + # Aliased as skip_before_filter. ## - # :method: append_before_filter + # :method: append_before_action # - # :call-seq: append_before_filter(names, block) + # :call-seq: append_before_action(names, block) # - # Append a before filter. See _insert_callbacks for parameter details. + # Append a callback before actions. See _insert_callbacks for parameter details. + # Aliased as append_before_filter. ## - # :method: after_filter + # :method: after_action # - # :call-seq: after_filter(names, block) + # :call-seq: after_action(names, block) # - # Append an after filter. See _insert_callbacks for parameter details. + # Append a callback after actions. See _insert_callbacks for parameter details. + # Aliased as after_filter. ## - # :method: prepend_after_filter + # :method: prepend_after_action # - # :call-seq: prepend_after_filter(names, block) + # :call-seq: prepend_after_action(names, block) # - # Prepend an after filter. See _insert_callbacks for parameter details. + # Prepend a callback after actions. See _insert_callbacks for parameter details. + # Aliased as prepend_after_filter. ## - # :method: skip_after_filter + # :method: skip_after_action # - # :call-seq: skip_after_filter(names) + # :call-seq: skip_after_action(names) # - # Skip an after filter. See _insert_callbacks for parameter details. + # Skip a callback after actions. See _insert_callbacks for parameter details. + # Aliased as skip_after_filter. ## - # :method: append_after_filter + # :method: append_after_action # - # :call-seq: append_after_filter(names, block) + # :call-seq: append_after_action(names, block) # - # Append an after filter. See _insert_callbacks for parameter details. + # Append a callback after actions. See _insert_callbacks for parameter details. + # Aliased as append_after_filter. ## - # :method: around_filter + # :method: around_action # - # :call-seq: around_filter(names, block) + # :call-seq: around_action(names, block) # - # Append an around filter. See _insert_callbacks for parameter details. + # Append a callback around actions. See _insert_callbacks for parameter details. + # Aliased as around_filter. ## - # :method: prepend_around_filter + # :method: prepend_around_action # - # :call-seq: prepend_around_filter(names, block) + # :call-seq: prepend_around_action(names, block) # - # Prepend an around filter. See _insert_callbacks for parameter details. + # Prepend a callback around actions. See _insert_callbacks for parameter details. + # Aliased as prepend_around_filter. ## - # :method: skip_around_filter + # :method: skip_around_action # - # :call-seq: skip_around_filter(names) + # :call-seq: skip_around_action(names) # - # Skip an around filter. See _insert_callbacks for parameter details. + # Skip a callback around actions. See _insert_callbacks for parameter details. + # Aliased as skip_around_filter. ## - # :method: append_around_filter + # :method: append_around_action # - # :call-seq: append_around_filter(names, block) + # :call-seq: append_around_action(names, block) # - # Append an around filter. See _insert_callbacks for parameter details. + # Append a callback around actions. See _insert_callbacks for parameter details. + # Aliased as append_around_filter. - # set up before_filter, prepend_before_filter, skip_before_filter, etc. + # set up before_action, prepend_before_action, skip_before_action, etc. # for each of before, after, and around. - [:before, :after, :around].each do |filter| + [:before, :after, :around].each do |callback| class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - # Append a before, after or around filter. See _insert_callbacks + # Append a before, after or around callback. See _insert_callbacks # 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| - set_callback(:process_action, :#{filter}, name, options) # set_callback(:process_action, :before, name, options) - end # end - end # end + def #{callback}_action(*names, &blk) # def before_action(*names, &blk) + _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{callback}, name, options) # set_callback(:process_action, :before, name, options) + end # end + end # end + + alias_method :#{callback}_filter, :#{callback}_action - # Prepend a before, after or around filter. See _insert_callbacks + # Prepend a before, after or around callback. See _insert_callbacks # 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| - set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) - end # end - end # end + def prepend_#{callback}_action(*names, &blk) # def prepend_before_action(*names, &blk) + _insert_callbacks(names, blk) do |name, options| # _insert_callbacks(names, blk) do |name, options| + set_callback(:process_action, :#{callback}, name, options.merge(:prepend => true)) # set_callback(:process_action, :before, name, options.merge(:prepend => true)) + end # end + end # end + + alias_method :prepend_#{callback}_filter, :prepend_#{callback}_action - # Skip a before, after or around filter. See _insert_callbacks + # Skip a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. - 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 - - # *_filter is the same as append_*_filter - alias_method :append_#{filter}_filter, :#{filter}_filter # alias_method :append_before_filter, :before_filter + def skip_#{callback}_action(*names) # def skip_before_action(*names) + _insert_callbacks(names) do |name, options| # _insert_callbacks(names) do |name, options| + skip_callback(:process_action, :#{callback}, name, options) # skip_callback(:process_action, :before, name, options) + end # end + end # end + + alias_method :skip_#{callback}_filter, :skip_#{callback}_action + + # *_action is the same as append_*_action + alias_method :append_#{callback}_action, :#{callback}_action # alias_method :append_before_action, :before_action + alias_method :append_#{callback}_filter, :#{callback}_action # alias_method :append_before_filter, :before_action RUBY_EVAL end end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index d4e73bf257..36a0dcb2de 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -19,7 +19,7 @@ module AbstractController def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } - klass.class_eval { default_helper_module! unless anonymous? } + klass.class_eval { default_helper_module! } unless klass.anonymous? super end diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb index c38d8ccef3..f1e8714a86 100644 --- a/actionpack/lib/action_controller/metal/force_ssl.rb +++ b/actionpack/lib/action_controller/metal/force_ssl.rb @@ -32,14 +32,14 @@ module ActionController # ==== 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>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 + before_action(options) do force_ssl_redirect(host) end end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index d2cbbd3330..35facd13c8 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -1,4 +1,3 @@ - module ActionController # The \Rails framework provides a large number of helpers for working with assets, dates, forms, # numbers and model objects, to name a few. These helpers are available to all templates @@ -91,11 +90,11 @@ module ActionController end def all_helpers_from_path(path) - helpers = [] - Array(path).each do |_path| - extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + helpers = Array(path).flat_map do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } - helpers += names.sort + names.sort! + names end helpers.uniq! helpers diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index d3b5bafee1..283f6413ec 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -25,7 +25,7 @@ module ActionController # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base - # before_filter :set_account, :authenticate + # before_action :set_account, :authenticate # # protected # def set_account @@ -68,7 +68,7 @@ module ActionController module ClassMethods def http_basic_authenticate_with(options = {}) - before_filter(options.except(:name, :password, :realm)) do + before_action(options.except(:name, :password, :realm)) do authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password| name == options[:name] && password == options[:password] end @@ -124,7 +124,7 @@ module ActionController # USERS = {"dhh" => "secret", #plain text password # "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password # - # before_filter :authenticate, except: [:index] + # before_action :authenticate, except: [:index] # # def index # render text: "Everyone can see me!" @@ -317,7 +317,7 @@ module ActionController # class PostsController < ApplicationController # TOKEN = "secret" # - # before_filter :authenticate, except: [ :index ] + # before_action :authenticate, except: [ :index ] # # def index # render text: "Everyone can see me!" @@ -340,7 +340,7 @@ module ActionController # the regular HTML interface is protected by a session approach: # # class ApplicationController < ActionController::Base - # before_filter :set_account, :authenticate + # before_action :set_account, :authenticate # # protected # def set_account diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index b23938e7d9..091facfd8d 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -74,7 +74,7 @@ module ActionController private def _extract_redirect_to_status(options, response_status) - status = if options.is_a?(Hash) && options.key?(:status) + if options.is_a?(Hash) && options.key?(:status) Rack::Utils.status_code(options.delete(:status)) elsif response_status.key?(:status) Rack::Utils.status_code(response_status[:status]) @@ -94,8 +94,7 @@ module ActionController when String request.protocol + request.host_with_port + options when :back - raise RedirectBackError unless refer = request.headers["Referer"] - refer + request.headers["Referer"] or raise RedirectBackError when Proc _compute_redirect_to_location options.call else diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 265ce5d6f3..c5db0cb0d4 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -19,7 +19,7 @@ module ActionController #:nodoc: # # class ApplicationController < ActionController::Base # protect_from_forgery - # skip_before_filter :verify_authenticity_token, if: :json_request? + # skip_before_action :verify_authenticity_token, if: :json_request? # # protected # @@ -66,15 +66,15 @@ module ActionController #:nodoc: # # You can disable csrf protection on controller-by-controller basis: # - # skip_before_filter :verify_authenticity_token + # skip_before_action :verify_authenticity_token # # It can also be disabled for specific controller actions: # - # skip_before_filter :verify_authenticity_token, except: [:create] + # skip_before_action :verify_authenticity_token, except: [:create] # # Valid Options: # - # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified. + # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -84,7 +84,7 @@ module ActionController #:nodoc: def protect_from_forgery(options = {}) include protection_method_module(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token - prepend_before_filter :verify_authenticity_token, options + prepend_before_action :verify_authenticity_token, options end private @@ -152,7 +152,7 @@ module ActionController #:nodoc: end protected - # The actual before_filter that is used. Modify this to change how you handle unverified requests. + # The actual before_action that is used. Modify this to change how you handle unverified requests. def verify_authenticity_token unless verified_request? logger.warn "Can't verify CSRF token authenticity" if logger diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 25e72adbe0..8faa5f8a13 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,5 +1,6 @@ require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' +require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' module ActionController diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 4a7df6b657..02ab49b44e 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -1,4 +1,3 @@ -require 'mutex_m' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/object/duplicable' @@ -21,8 +20,6 @@ module ActionDispatch # end # => reverses the value to all keys matching /secret/i module FilterParameters - @@parameter_filter_for = {}.extend(Mutex_m) - ENV_MATCH = [/RAW_POST_DATA/, "rack.request.form_vars"] # :nodoc: NULL_PARAM_FILTER = ParameterFilter.new # :nodoc: NULL_ENV_FILTER = ParameterFilter.new ENV_MATCH # :nodoc: @@ -65,11 +62,7 @@ module ActionDispatch end def parameter_filter_for(filters) - @@parameter_filter_for.synchronize do - # Do we *actually* need this cache? Constructing ParameterFilters - # doesn't seem too expensive. - @@parameter_filter_for[filters] ||= ParameterFilter.new(filters) - end + ParameterFilter.new(filters) end KV_RE = '[^&;=]+' @@ -79,7 +72,6 @@ module ActionDispatch parameter_filter.filter([[$1, $2]]).first.join("=") end end - end end end diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index 0f98e84788..57660e93c4 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -68,7 +68,7 @@ module ActionDispatch # that are not controlled by the extension. # # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone + # before_action :adjust_format_for_iphone # # private # def adjust_format_for_iphone @@ -87,7 +87,7 @@ module ActionDispatch # to the :html format. # # class ApplicationController < ActionController::Base - # before_filter :adjust_format_for_iphone_with_html_fallback + # before_action :adjust_format_for_iphone_with_html_fallback # # private # def adjust_format_for_iphone_with_html_fallback diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 9a7b5bc8c7..6610315da7 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -12,7 +12,11 @@ module ActionDispatch # Returns both GET and POST \parameters in a single hash. def parameters @env["action_dispatch.request.parameters"] ||= begin - params = request_parameters.merge(query_parameters) + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end params.merge!(path_parameters) encode_params(params).with_indifferent_access end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 3de927abc8..d60c8775af 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -205,8 +205,9 @@ module ActionDispatch # work with raw requests directly. def raw_post unless @env.include? 'RAW_POST_DATA' - @env['RAW_POST_DATA'] = body.read(@env['CONTENT_LENGTH'].to_i) - body.rewind if body.respond_to?(:rewind) + raw_post_body = body + @env['RAW_POST_DATA'] = raw_post_body.read(@env['CONTENT_LENGTH'].to_i) + raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end @env['RAW_POST_DATA'] end diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index c18dc94d4f..8d7461ecc3 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -51,7 +51,7 @@ module ActionDispatch end def internal? - path =~ %r{/rails/info.*|^#{Rails.application.config.assets.prefix}} + controller =~ %r{\Arails/(info|welcome)} || path =~ %r{\A#{Rails.application.config.assets.prefix}} end def engine? diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 0f95daa790..eb9d4b24f1 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,5 +1,6 @@ require 'journey' require 'forwardable' +require 'thread_safe' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/module/remove_method' @@ -20,7 +21,7 @@ module ActionDispatch def initialize(options={}) @defaults = options[:defaults] @glob_param = options.delete(:glob) - @controllers = {} + @controller_class_names = ThreadSafe::Cache.new end def call(env) @@ -68,13 +69,8 @@ module ActionDispatch private def controller_reference(controller_param) - controller_name = "#{controller_param.camelize}Controller" - - unless controller = @controllers[controller_param] - controller = @controllers[controller_param] = - ActiveSupport::Dependencies.reference(controller_name) - end - controller.get(controller_name) + const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" + ActiveSupport::Dependencies.constantize(const_name) end def dispatch(controller, action, env) @@ -130,6 +126,12 @@ module ActionDispatch end def clear! + @helpers.each do |helper| + @module.module_eval do + remove_possible_method helper + end + end + @routes.clear @helpers.clear end @@ -288,7 +290,6 @@ module ActionDispatch def clear! @finalized = false - @url_helpers = nil named_routes.clear set.clear formatter.clear diff --git a/actionpack/lib/action_view/digestor.rb b/actionpack/lib/action_view/digestor.rb index 1c6eaf36f7..8bc69b9246 100644 --- a/actionpack/lib/action_view/digestor.rb +++ b/actionpack/lib/action_view/digestor.rb @@ -1,4 +1,4 @@ -require 'mutex_m' +require 'thread_safe' module ActionView class Digestor @@ -21,23 +21,12 @@ module ActionView /x cattr_reader(:cache) - @@cache = Hash.new.extend Mutex_m + @@cache = ThreadSafe::Cache.new def self.digest(name, format, finder, options = {}) - cache.synchronize do - unsafe_digest name, format, finder, options - end - end - - ### - # This method is NOT thread safe. DO NOT CALL IT DIRECTLY, instead call - # Digestor.digest - def self.unsafe_digest(name, format, finder, options = {}) # :nodoc: - key = "#{name}.#{format}" - - cache.fetch(key) do + @@cache["#{name}.#{format}"] ||= begin klass = options[:partial] || name.include?("/_") ? PartialDigestor : Digestor - cache[key] = klass.new(name, format, finder).digest + klass.new(name, format, finder).digest end end @@ -93,7 +82,7 @@ module ActionView def dependency_digest dependencies.collect do |template_name| - Digestor.unsafe_digest(template_name, format, finder, partial: true) + Digestor.digest(template_name, format, finder, partial: true) end.join("-") end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 8693f4f0e4..995aa10afb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -110,15 +110,8 @@ module ActionView # <%= some_helper_method(person) %> # # Now all you'll have to do is change that timestamp when the helper method changes. - # - # ==== Conditional caching - # - # You can pass :if and :unless options, to conditionally perform or skip the cache. - # - # <%= cache @model, if: some_condition(@model) do %> - # def cache(name = {}, options = nil, &block) - if controller.perform_caching && conditions_match?(options) + if controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) else yield @@ -127,6 +120,32 @@ module ActionView nil end + # Cache fragments of a view if +condition+ is true + # + # <%= cache_if admin?, project do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> + # <% end %> + def cache_if(condition, name = {}, options = nil, &block) + if condition + cache(name, options, &block) + else + yield + end + + nil + end + + # Cache fragments of a view unless +condition+ is true + # + # <%= cache_unless admin?, project do %> + # <b>All the topics on this project</b> + # <%= render project.topics %> + # <% end %> + def cache_unless(condition, name = {}, options = nil, &block) + cache_if !condition, name, options, &block + end + # This helper returns the name of a cache key for a given fragment cache # call. By supplying skip_digest: true to cache, the digestion of cache # fragments can be manually bypassed. This is useful when cache fragments @@ -144,10 +163,6 @@ module ActionView private - def conditions_match?(options) - !(options && (!options.fetch(:if, true) || options.fetch(:unless, false))) - end - def fragment_name_with_digest(name) #:nodoc: if @virtual_path [ diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 17386a57b8..516492ca30 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -775,8 +775,8 @@ module ActionView # text_field(:post, :title, class: "create_input") # # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" /> # - # text_field(:session, :user, onchange: "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }") - # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('session[user]').value == 'admin' { alert('Your login can not be admin!'); }"/> + # text_field(:session, :user, onchange: "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }") + # # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange = "if $('#session_user').value == 'admin' { alert('Your login can not be admin!'); }"/> # # text_field(:snippet, :code, size: 20, class: 'code_input') # # => <input type="text" id="snippet_code" name="snippet[code]" size="20" value="#{@snippet.code}" class="code_input" /> @@ -830,13 +830,25 @@ module ActionView # # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. # + # ==== Options + # * Creates standard HTML attributes for the tag. + # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. + # # ==== Examples # file_field(:user, :avatar) # # => <input type="file" id="user_avatar" name="user[avatar]" /> # + # file_field(:post, :image, :multiple => true) + # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> + # # file_field(:post, :attached, accept: 'text/html') # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> # + # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') + # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> + # # file_field(:attachment, :file, class: 'file_input') # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> def file_field(object_name, method, options = {}) @@ -1214,6 +1226,255 @@ module ActionView RUBY_EVAL end + # Creates a scope around a specific model object like form_for, but + # doesn't create the form tags themselves. This makes fields_for suitable + # for specifying additional model objects in the same form. + # + # 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 :permission, @person.permission do |permission_fields| %> + # Admin? : <%= permission_fields.check_box :admin %> + # <% end %> + # + # <%= f.submit %> + # <% end %> + # + # 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 :permission do |permission_fields| %> + # Admin?: <%= permission_fields.check_box :admin %> + # <% end %> + # + # ...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>. + # + # 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. + # + # === Nested Attributes Examples + # + # When the object belonging to the current scope has a nested attribute + # writer for a certain attribute, fields_for will yield a new scope + # for that attribute. This allows you to create forms that set or change + # the attributes of a parent object and its associations in one go. + # + # Nested attribute writers are normal setter methods named after an + # association. The most common way of defining these writers is either + # with +accepts_nested_attributes_for+ in a model definition or by + # defining a method with the proper name. For example: the attribute + # writer for the association <tt>:address</tt> is called + # <tt>address_attributes=</tt>. + # + # Whether a one-to-one or one-to-many style form builder will be yielded + # depends on whether the normal reader method returns a _single_ object + # or an _array_ of objects. + # + # ==== One-to-one + # + # Consider a Person class which returns a _single_ Address from the + # <tt>address</tt> reader method and responds to the + # <tt>address_attributes=</tt> writer method: + # + # class Person + # def address + # @address + # end + # + # def address_attributes=(attributes) + # # Process the attributes hash + # end + # end + # + # This model can now be used with a nested fields_for, like so: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :address do |address_fields| %> + # Street : <%= address_fields.text_field :street %> + # Zip code: <%= address_fields.text_field :zip_code %> + # <% end %> + # ... + # <% end %> + # + # When address is already an association on a Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_one :address + # accepts_nested_attributes_for :address + # end + # + # If you want to destroy the associated model through the form, you have + # to enable it first using the <tt>:allow_destroy</tt> option for + # +accepts_nested_attributes_for+: + # + # class Person < ActiveRecord::Base + # has_one :address + # accepts_nested_attributes_for :address, allow_destroy: true + # end + # + # Now, when you use a form element with the <tt>_destroy</tt> parameter, + # with a value that evaluates to +true+, you will destroy the associated + # model (eg. 1, '1', true, or 'true'): + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :address do |address_fields| %> + # ... + # Delete: <%= address_fields.check_box :_destroy %> + # <% end %> + # ... + # <% end %> + # + # ==== One-to-many + # + # Consider a Person class which returns an _array_ of Project instances + # from the <tt>projects</tt> reader method and responds to the + # <tt>projects_attributes=</tt> writer method: + # + # class Person + # def projects + # [@project1, @project2] + # end + # + # def projects_attributes=(attributes) + # # Process the attributes hash + # end + # end + # + # Note that the <tt>projects_attributes=</tt> writer method is in fact + # required for fields_for to correctly identify <tt>:projects</tt> as a + # collection, and the correct indices to be set in the form markup. + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # + # This model can now be used with a nested fields_for. The block given to + # the nested fields_for call will be repeated for each instance in the + # collection: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # <% if project_fields.object.active? %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # <% end %> + # ... + # <% end %> + # + # It's also possible to specify the instance to be used: + # + # <%= form_for @person do |person_form| %> + # ... + # <% @person.projects.each do |project| %> + # <% if project.active? %> + # <%= person_form.fields_for :projects, project do |project_fields| %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # <% end %> + # <% end %> + # ... + # <% end %> + # + # Or a collection to be used: + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> + # Name: <%= project_fields.text_field :name %> + # <% end %> + # ... + # <% end %> + # + # When projects is already an association on Person you can use + # +accepts_nested_attributes_for+ to define the writer method for you: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects + # end + # + # If you want to destroy any of the associated models through the + # form, you have to enable it first using the <tt>:allow_destroy</tt> + # option for +accepts_nested_attributes_for+: + # + # class Person < ActiveRecord::Base + # has_many :projects + # accepts_nested_attributes_for :projects, allow_destroy: true + # end + # + # This will allow you to specify which models to destroy in the + # attributes hash by adding a form element for the <tt>_destroy</tt> + # parameter with a value that evaluates to +true+ + # (eg. 1, '1', true, or 'true'): + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # Delete: <%= project_fields.check_box :_destroy %> + # <% end %> + # ... + # <% end %> + # + # When a collection is used you might want to know the index of each + # object into the array. For this purpose, the <tt>index</tt> method + # is available in the FormBuilder object. + # + # <%= form_for @person do |person_form| %> + # ... + # <%= person_form.fields_for :projects do |project_fields| %> + # Project #<%= project_fields.index %> + # ... + # <% end %> + # ... + # <% end %> + # + # Note that fields_for will automatically generate a hidden field + # to store the ID of the record. There are circumstances where this + # hidden field is not needed and you can pass <tt>hidden_field_id: false</tt> + # to prevent fields_for from rendering it automatically. def fields_for(record_name, record_object = nil, fields_options = {}, &block) fields_options, record_object = record_object, nil if record_object.is_a?(Hash) && record_object.extractable_options? fields_options[:builder] ||= options[:builder] @@ -1243,23 +1504,186 @@ module ActionView @template.fields_for(record_name, record_object, fields_options, &block) end + # Returns a label tag tailored for labelling an input field for a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). The text of label will default to the attribute name unless a translation + # is found in the current I18n locale (through helpers.label.<modelname>.<attribute>) or you specify it explicitly. + # Additional options on the label tag can be passed as a hash with +options+. These options will be tagged + # onto the HTML as an HTML element attribute as in the example shown, except for the <tt>:value</tt> option, which is designed to + # target labels for radio_button tags (where the value is used in the ID of the input tag). + # + # ==== Examples + # label(:post, :title) + # # => <label for="post_title">Title</label> + # + # You can localize your labels based on model and attribute names. + # For example you can define the following in your locale (e.g. en.yml) + # + # helpers: + # label: + # post: + # body: "Write your entire text here" + # + # Which then will result in + # + # label(:post, :body) + # # => <label for="post_body">Write your entire text here</label> + # + # Localization can also be based purely on the translation of the attribute-name + # (if you are using ActiveRecord): + # + # activerecord: + # attributes: + # post: + # cost: "Total cost" + # + # label(:post, :cost) + # # => <label for="post_cost">Total cost</label> + # + # label(:post, :title, "A short title") + # # => <label for="post_title">A short title</label> + # + # label(:post, :title, "A short title", class: "title_label") + # # => <label for="post_title" class="title_label">A short title</label> + # + # label(:post, :privacy, "Public Post", value: "public") + # # => <label for="post_privacy_public">Public Post</label> + # + # label(:post, :terms) do + # 'Accept <a href="/terms">Terms</a>.'.html_safe + # end def label(method, text = nil, options = {}, &block) @template.label(@object_name, method, text, objectify_options(options), &block) end + # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). This object must be an instance object (@object) and not a local object. + # It's intended that +method+ returns an integer and if that integer is above zero, then the checkbox is checked. + # Additional options on the input tag can be passed as a hash with +options+. The +checked_value+ defaults to 1 + # while the default +unchecked_value+ is set to 0 which is convenient for boolean values. + # + # ==== Gotcha + # + # The HTML specification says unchecked check boxes are not successful, and + # thus web browsers do not send them. Unfortunately this introduces a gotcha: + # if an +Invoice+ model has a +paid+ flag, and in the form that edits a paid + # invoice the user unchecks its check box, no +paid+ parameter is sent. So, + # any mass-assignment idiom like + # + # @invoice.update_attributes(params[:invoice]) + # + # wouldn't update the flag. + # + # To prevent this the helper generates an auxiliary hidden field before + # the very check box. The hidden field has the same name and its + # attributes mimic an unchecked check box. + # + # This way, the client either sends only the hidden field (representing + # the check box is unchecked), or both fields. Since the HTML specification + # says key/value pairs have to be sent in the same order they appear in the + # form, and parameters extraction gets the last occurrence of any repeated + # key in the query string, that works for ordinary forms. + # + # Unfortunately that workaround does not work when the check box goes + # within an array-like parameter, as in + # + # <%= fields_for "project[invoice_attributes][]", invoice, index: nil do |form| %> + # <%= form.check_box :paid %> + # ... + # <% end %> + # + # because parameter name repetition is precisely what Rails seeks to distinguish + # the elements of the array. For each item with a checked check box you + # get an extra ghost item with only that attribute, assigned to "0". + # + # In that case it is preferable to either use +check_box_tag+ or to use + # hashes instead of arrays. + # + # # Let's say that @post.validated? is 1: + # check_box("post", "validated") + # # => <input name="post[validated]" type="hidden" value="0" /> + # # <input checked="checked" type="checkbox" id="post_validated" name="post[validated]" value="1" /> + # + # # Let's say that @puppy.gooddog is "no": + # check_box("puppy", "gooddog", {}, "yes", "no") + # # => <input name="puppy[gooddog]" type="hidden" value="no" /> + # # <input type="checkbox" id="puppy_gooddog" name="puppy[gooddog]" value="yes" /> + # + # check_box("eula", "accepted", { class: 'eula_check' }, "yes", "no") + # # => <input name="eula[accepted]" type="hidden" value="no" /> + # # <input type="checkbox" class="eula_check" id="eula_accepted" name="eula[accepted]" value="yes" /> def check_box(method, options = {}, checked_value = "1", unchecked_value = "0") @template.check_box(@object_name, method, objectify_options(options), checked_value, unchecked_value) end + # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the + # radio button will be checked. + # + # To force the radio button to be checked pass <tt>checked: true</tt> in the + # +options+ hash. You may pass HTML options there as well. + # + # # Let's say that @post.category returns "rails": + # radio_button("post", "category", "rails") + # radio_button("post", "category", "java") + # # => <input type="radio" id="post_category_rails" name="post[category]" value="rails" checked="checked" /> + # # <input type="radio" id="post_category_java" name="post[category]" value="java" /> + # + # radio_button("user", "receive_newsletter", "yes") + # radio_button("user", "receive_newsletter", "no") + # # => <input type="radio" id="user_receive_newsletter_yes" name="user[receive_newsletter]" value="yes" /> + # # <input type="radio" id="user_receive_newsletter_no" name="user[receive_newsletter]" value="no" checked="checked" /> def radio_button(method, tag_value, options = {}) @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end + # Returns a hidden input tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a + # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example + # shown. + # + # ==== Examples + # hidden_field(:signup, :pass_confirm) + # # => <input type="hidden" id="signup_pass_confirm" name="signup[pass_confirm]" value="#{@signup.pass_confirm}" /> + # + # hidden_field(:post, :tag_list) + # # => <input type="hidden" id="post_tag_list" name="post[tag_list]" value="#{@post.tag_list}" /> + # + # hidden_field(:user, :token) + # # => <input type="hidden" id="user_token" name="user[token]" value="#{@user.token}" /> + # def hidden_field(method, options = {}) @emitted_hidden_id = true if method == :id @template.hidden_field(@object_name, method, objectify_options(options)) end + # Returns a file upload input tag tailored for accessing a specified attribute (identified by +method+) on an object + # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a + # hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example + # shown. + # + # Using this method inside a +form_for+ block will set the enclosing form's encoding to <tt>multipart/form-data</tt>. + # + # ==== Options + # * Creates standard HTML attributes for the tag. + # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. + # + # ==== Examples + # file_field(:user, :avatar) + # # => <input type="file" id="user_avatar" name="user[avatar]" /> + # + # file_field(:post, :image, :multiple => true) + # # => <input type="file" id="post_image" name="post[image]" multiple="true" /> + # + # file_field(:post, :attached, accept: 'text/html') + # # => <input accept="text/html" type="file" id="post_attached" name="post[attached]" /> + # + # file_field(:post, :image, accept: 'image/png,image/gif,image/jpeg') + # # => <input type="file" id="post_image" name="post[image]" accept="image/png,image/gif,image/jpeg" /> + # + # file_field(:attachment, :file, class: 'file_input') + # # => <input type="file" id="attachment_file" name="attachment[file]" class="file_input" /> def file_field(method, options = {}) self.multipart = true @template.file_field(@object_name, method, objectify_options(options)) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index c0e7ee1f8d..1b5b788a35 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -2,6 +2,7 @@ require 'cgi' require 'erb' require 'action_view/helpers/form_helper' require 'active_support/core_ext/string/output_safety' +require 'active_support/core_ext/array/wrap' module ActionView # = Action View Form Option Helpers diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index e298751062..ff83ef3ca1 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -233,6 +233,8 @@ module ActionView # ==== Options # * Creates standard HTML attributes for the tag. # * <tt>:disabled</tt> - If set to true, the user will not be able to use this input. + # * <tt>:multiple</tt> - If set to true, *in most updated browsers* the user will be allowed to select multiple files. + # * <tt>:accept</tt> - If set to one or multiple mime-types, the user will be suggested a filter when choosing a file. You still need to set up model validations. # # ==== Examples # file_field_tag 'attachment' diff --git a/actionpack/lib/action_view/helpers/tags/date_select.rb b/actionpack/lib/action_view/helpers/tags/date_select.rb index 5d706087b0..6c400f85cb 100644 --- a/actionpack/lib/action_view/helpers/tags/date_select.rb +++ b/actionpack/lib/action_view/helpers/tags/date_select.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/time/calculations' + module ActionView module Helpers module Tags @@ -58,7 +60,7 @@ module ActionView default[key] ||= time.send(key) end - Time.utc_time( + Time.utc( default[:year], default[:month], default[:day], default[:hour], default[:min], default[:sec] ) diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb index 76f4dea7b8..4e4816d983 100644 --- a/actionpack/lib/action_view/lookup_context.rb +++ b/actionpack/lib/action_view/lookup_context.rb @@ -1,3 +1,4 @@ +require 'thread_safe' require 'active_support/core_ext/module/remove_method' module ActionView @@ -51,7 +52,7 @@ module ActionView alias :object_hash :hash attr_reader :hash - @details_keys = Hash.new + @details_keys = ThreadSafe::Cache.new def self.get(details) @details_keys[details] ||= new diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 8fb9b6ff18..37f93a13fc 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,3 +1,5 @@ +require 'thread_safe' + module ActionView # = Action View Partials # @@ -247,7 +249,9 @@ module ActionView # <%- end -%> # <% end %> class PartialRenderer < AbstractRenderer - PREFIXED_PARTIAL_NAMES = Hash.new { |h,k| h[k] = {} } + PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k| + h[k] = ThreadSafe::Cache.new + end def initialize(*) super diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index fc77c1485d..8b23029bbc 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -3,7 +3,7 @@ require "active_support/core_ext/class" require "active_support/core_ext/class/attribute_accessors" require "action_view/template" require "thread" -require "mutex_m" +require "thread_safe" module ActionView # = Action View Resolver @@ -35,52 +35,51 @@ module ActionView # Threadsafe template cache class Cache #:nodoc: - class CacheEntry - include Mutex_m - - attr_accessor :templates + class SmallCache < ThreadSafe::Cache + def initialize(options = {}) + super(options.merge(:initial_capacity => 2)) + end end + # preallocate all the default blocks for performance/memory consumption reasons + PARTIAL_BLOCK = lambda {|cache, partial| cache[partial] = SmallCache.new} + PREFIX_BLOCK = lambda {|cache, prefix| cache[prefix] = SmallCache.new(&PARTIAL_BLOCK)} + NAME_BLOCK = lambda {|cache, name| cache[name] = SmallCache.new(&PREFIX_BLOCK)} + KEY_BLOCK = lambda {|cache, key| cache[key] = SmallCache.new(&NAME_BLOCK)} + + # usually a majority of template look ups return nothing, use this canonical preallocated array to safe memory + NO_TEMPLATES = [].freeze + def initialize - @data = Hash.new { |h1,k1| h1[k1] = Hash.new { |h2,k2| - h2[k2] = Hash.new { |h3,k3| h3[k3] = Hash.new { |h4,k4| h4[k4] = {} } } } } - @mutex = Mutex.new + @data = SmallCache.new(&KEY_BLOCK) end # Cache the templates returned by the block def cache(key, name, prefix, partial, locals) - cache_entry = nil - - # first obtain a lock on the main data structure to create the cache entry - @mutex.synchronize do - cache_entry = @data[key][name][prefix][partial][locals] ||= CacheEntry.new - end - - # then to avoid a long lasting global lock, obtain a more granular lock - # on the CacheEntry itself - cache_entry.synchronize do - if Resolver.caching? - cache_entry.templates ||= yield + if Resolver.caching? + @data[key][name][prefix][partial][locals] ||= canonical_no_templates(yield) + else + fresh_templates = yield + cached_templates = @data[key][name][prefix][partial][locals] + + if templates_have_changed?(cached_templates, fresh_templates) + @data[key][name][prefix][partial][locals] = canonical_no_templates(fresh_templates) else - fresh_templates = yield - - if templates_have_changed?(cache_entry.templates, fresh_templates) - cache_entry.templates = fresh_templates - else - cache_entry.templates ||= [] - end + cached_templates || NO_TEMPLATES end end end def clear - @mutex.synchronize do - @data.clear - end + @data.clear end private + def canonical_no_templates(templates) + templates.empty? ? NO_TEMPLATES : templates + end + def templates_have_changed?(cached_templates, fresh_templates) # if either the old or new template list is empty, we don't need to (and can't) # compare modification times, and instead just check whether the lists are different diff --git a/actionpack/test/abstract/callbacks_test.rb b/actionpack/test/abstract/callbacks_test.rb index 5d1a703c55..1090af3060 100644 --- a/actionpack/test/abstract/callbacks_test.rb +++ b/actionpack/test/abstract/callbacks_test.rb @@ -28,9 +28,9 @@ module AbstractController end class Callback2 < ControllerWithCallbacks - before_filter :first - after_filter :second - around_filter :aroundz + before_action :first + after_action :second + around_action :aroundz def first @text = "Hello world" @@ -53,7 +53,7 @@ module AbstractController end class Callback2Overwrite < Callback2 - before_filter :first, :except => :index + before_action :first, except: :index end class TestCallbacks2 < ActiveSupport::TestCase @@ -61,22 +61,22 @@ module AbstractController @controller = Callback2.new end - test "before_filter works" do + test "before_action works" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end - test "after_filter works" do + test "after_action works" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end - test "around_filter works" do + test "around_action works" do @controller.process(:index) assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") end - test "before_filter with overwritten condition" do + test "before_action with overwritten condition" do @controller = Callback2Overwrite.new @controller.process(:index) assert_equal "", @controller.response_body @@ -84,11 +84,11 @@ module AbstractController end class Callback3 < ControllerWithCallbacks - before_filter do |c| + before_action do |c| c.instance_variable_set("@text", "Hello world") end - after_filter do |c| + after_action do |c| c.instance_variable_set("@second", "Goodbye") end @@ -102,20 +102,20 @@ module AbstractController @controller = Callback3.new end - test "before_filter works with procs" do + test "before_action works with procs" do @controller.process(:index) assert_equal "Hello world", @controller.response_body end - test "after_filter works with procs" do + test "after_action works with procs" do @controller.process(:index) assert_equal "Goodbye", @controller.instance_variable_get("@second") end end class CallbacksWithConditions < ControllerWithCallbacks - before_filter :list, :only => :index - before_filter :authenticate, :except => :index + before_action :list, :only => :index + before_action :authenticate, :except => :index def index self.response_body = @list.join(", ") @@ -141,25 +141,25 @@ module AbstractController @controller = CallbacksWithConditions.new end - test "when :only is specified, a before filter is triggered on that action" do + test "when :only is specified, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end - test "when :only is specified, a before filter is not triggered on other actions" do + test "when :only is specified, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end - test "when :except is specified, an after filter is not triggered on that action" do + test "when :except is specified, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class CallbacksWithArrayConditions < ControllerWithCallbacks - before_filter :list, :only => [:index, :listy] - before_filter :authenticate, :except => [:index, :listy] + before_action :list, only: [:index, :listy] + before_action :authenticate, except: [:index, :listy] def index self.response_body = @list.join(", ") @@ -185,24 +185,24 @@ module AbstractController @controller = CallbacksWithArrayConditions.new end - test "when :only is specified with an array, a before filter is triggered on that action" do + test "when :only is specified with an array, a before action is triggered on that action" do @controller.process(:index) assert_equal "Hello, World", @controller.response_body end - test "when :only is specified with an array, a before filter is not triggered on other actions" do + test "when :only is specified with an array, a before action is not triggered on other actions" do @controller.process(:sekrit_data) assert_equal "true", @controller.response_body end - test "when :except is specified with an array, an after filter is not triggered on that action" do + test "when :except is specified with an array, an after action is not triggered on that action" do @controller.process(:index) assert !@controller.instance_variable_defined?("@authenticated") end end class ChangedConditions < Callback2 - before_filter :first, :only => :index + before_action :first, :only => :index def not_index @text ||= nil @@ -227,7 +227,7 @@ module AbstractController end class SetsResponseBody < ControllerWithCallbacks - before_filter :set_body + before_action :set_body def index self.response_body = "Fail" @@ -266,6 +266,50 @@ module AbstractController end end + class AliasedCallbacks < ControllerWithCallbacks + before_filter :first + after_filter :second + around_filter :aroundz + + def first + @text = "Hello world" + end + + def second + @second = "Goodbye" + end + + def aroundz + @aroundz = "FIRST" + yield + @aroundz << "SECOND" + end + def index + @text ||= nil + self.response_body = @text.to_s + end + end + + class TestAliasedCallbacks < ActiveSupport::TestCase + def setup + @controller = AliasedCallbacks.new + end + + test "before_filter works" do + @controller.process(:index) + assert_equal "Hello world", @controller.response_body + end + + test "after_filter works" do + @controller.process(:index) + assert_equal "Goodbye", @controller.instance_variable_get("@second") + end + + test "around_filter works" do + @controller.process(:index) + assert_equal "FIRSTSECOND", @controller.instance_variable_get("@aroundz") + end + end end end diff --git a/actionpack/test/controller/default_url_options_with_filter_test.rb b/actionpack/test/controller/default_url_options_with_before_action_test.rb index 9a9ab17fee..656fd0431e 100644 --- a/actionpack/test/controller/default_url_options_with_filter_test.rb +++ b/actionpack/test/controller/default_url_options_with_before_action_test.rb @@ -1,10 +1,10 @@ require 'abstract_unit' -class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base +class ControllerWithBeforeActionAndDefaultUrlOptions < ActionController::Base - before_filter { I18n.locale = params[:locale] } - after_filter { I18n.locale = "en" } + before_action { I18n.locale = params[:locale] } + after_action { I18n.locale = "en" } def target render :text => "final response" @@ -19,11 +19,11 @@ class ControllerWithBeforeFilterAndDefaultUrlOptions < ActionController::Base end end -class ControllerWithBeforeFilterAndDefaultUrlOptionsTest < ActionController::TestCase +class ControllerWithBeforeActionAndDefaultUrlOptionsTest < ActionController::TestCase # This test has its roots in issue #1872 test "should redirect with correct locale :de" do get :redirect, :locale => "de" - assert_redirected_to "/controller_with_before_filter_and_default_url_options/target?locale=de" + assert_redirected_to "/controller_with_before_action_and_default_url_options/target?locale=de" end end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 6414ba3994..9d4356f546 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -53,8 +53,8 @@ class FlashTest < ActionController::TestCase render :inline => "hello" end - # methods for test_sweep_after_halted_filter_chain - before_filter :halt_and_redir, :only => "filter_halting_action" + # methods for test_sweep_after_halted_action_chain + before_action :halt_and_redir, only: 'filter_halting_action' def std_action @flash_copy = {}.update(flash) @@ -159,7 +159,7 @@ class FlashTest < ActionController::TestCase assert_nil session["flash"] end - def test_sweep_after_halted_filter_chain + def test_sweep_after_halted_action_chain get :std_action assert_nil assigns["flash_copy"]["foo"] get :filter_halting_action diff --git a/actionpack/test/controller/http_basic_authentication_test.rb b/actionpack/test/controller/http_basic_authentication_test.rb index 2dcfda02a7..90548d4294 100644 --- a/actionpack/test/controller/http_basic_authentication_test.rb +++ b/actionpack/test/controller/http_basic_authentication_test.rb @@ -2,9 +2,9 @@ require 'abstract_unit' class HttpBasicAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display - before_filter :authenticate_long_credentials, :only => :show + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display + before_action :authenticate_long_credentials, only: :show http_basic_authenticate_with :name => "David", :password => "Goliath", :only => :search diff --git a/actionpack/test/controller/http_digest_authentication_test.rb b/actionpack/test/controller/http_digest_authentication_test.rb index c4a94264c3..537de7a2dd 100644 --- a/actionpack/test/controller/http_digest_authentication_test.rb +++ b/actionpack/test/controller/http_digest_authentication_test.rb @@ -4,8 +4,8 @@ require 'active_support/key_generator' class HttpDigestAuthenticationTest < ActionController::TestCase class DummyDigestController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display USERS = { 'lifo' => 'world', 'pretty' => 'please', 'dhh' => ::Digest::MD5::hexdigest(["dhh","SuperSecret","secret"].join(":"))} diff --git a/actionpack/test/controller/http_token_authentication_test.rb b/actionpack/test/controller/http_token_authentication_test.rb index ad4e743be8..8a409d6ed2 100644 --- a/actionpack/test/controller/http_token_authentication_test.rb +++ b/actionpack/test/controller/http_token_authentication_test.rb @@ -2,9 +2,9 @@ require 'abstract_unit' class HttpTokenAuthenticationTest < ActionController::TestCase class DummyController < ActionController::Base - before_filter :authenticate, :only => :index - before_filter :authenticate_with_request, :only => :display - before_filter :authenticate_long_credentials, :only => :show + before_action :authenticate, only: :index + before_action :authenticate_with_request, only: :display + before_action :authenticate_long_credentials, only: :show def index render :text => "Hello Secret" diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb index d8b971f963..075347be52 100644 --- a/actionpack/test/controller/log_subscriber_test.rb +++ b/actionpack/test/controller/log_subscriber_test.rb @@ -13,7 +13,7 @@ module Another head :status => 406 end - before_filter :redirector, :only => :never_executed + before_action :redirector, only: :never_executed def never_executed end @@ -46,20 +46,20 @@ module Another render :inline => "<%= cache('foo%bar'){ 'Contains % sign in key' } %>" end - def with_fragment_cache_and_if_true_condition - render :inline => "<%= cache('foo', :if => true) { 'bar' } %>" + def with_fragment_cache_if_with_true_condition + render :inline => "<%= cache_if(true, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_if_false_condition - render :inline => "<%= cache('foo', :if => false) { 'bar' } %>" + def with_fragment_cache_if_with_false_condition + render :inline => "<%= cache_if(false, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_unless_false_condition - render :inline => "<%= cache('foo', :unless => false) { 'bar' } %>" + def with_fragment_cache_unless_with_false_condition + render :inline => "<%= cache_unless(false, 'foo') { 'bar' } %>" end - def with_fragment_cache_and_unless_true_condition - render :inline => "<%= cache('foo', :unless => true) { 'bar' } %>" + def with_fragment_cache_unless_with_true_condition + render :inline => "<%= cache_unless(true, 'foo') { 'bar' } %>" end def with_exception @@ -219,9 +219,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_if_true + def test_with_fragment_cache_if_with_true @controller.config.perform_caching = true - get :with_fragment_cache_and_if_true_condition + get :with_fragment_cache_if_with_true_condition wait assert_equal 4, logs.size @@ -231,9 +231,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_if_false + def test_with_fragment_cache_if_with_false @controller.config.perform_caching = true - get :with_fragment_cache_and_if_false_condition + get :with_fragment_cache_if_with_false_condition wait assert_equal 2, logs.size @@ -243,9 +243,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_unless_true + def test_with_fragment_cache_unless_with_true @controller.config.perform_caching = true - get :with_fragment_cache_and_unless_true_condition + get :with_fragment_cache_unless_with_true_condition wait assert_equal 2, logs.size @@ -255,9 +255,9 @@ class ACLogSubscriberTest < ActionController::TestCase @controller.config.perform_caching = true end - def test_with_fragment_cache_and_unless_false + def test_with_fragment_cache_unless_with_false @controller.config.perform_caching = true - get :with_fragment_cache_and_unless_false_condition + get :with_fragment_cache_unless_with_false_condition wait assert_equal 4, logs.size diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb index d183b0be17..ed013e2185 100644 --- a/actionpack/test/controller/mime_responds_test.rb +++ b/actionpack/test/controller/mime_responds_test.rb @@ -1139,7 +1139,7 @@ end # For testing layouts which are set automatically class PostController < AbstractPostController - around_filter :with_iphone + around_action :with_iphone def index respond_to(:html, :iphone, :js) diff --git a/actionpack/test/controller/new_base/base_test.rb b/actionpack/test/controller/new_base/base_test.rb index ed244513a5..964f22eb03 100644 --- a/actionpack/test/controller/new_base/base_test.rb +++ b/actionpack/test/controller/new_base/base_test.rb @@ -3,7 +3,7 @@ require 'abstract_unit' # Tests the controller dispatching happy path module Dispatching class SimpleController < ActionController::Base - before_filter :authenticate + before_action :authenticate def index render :text => "success" diff --git a/actionpack/test/controller/new_base/render_context_test.rb b/actionpack/test/controller/new_base/render_context_test.rb index f41b14d5d6..177a1c088d 100644 --- a/actionpack/test/controller/new_base/render_context_test.rb +++ b/actionpack/test/controller/new_base/render_context_test.rb @@ -14,7 +14,7 @@ module RenderContext include ActionView::Context # 2) Call _prepare_context that will do the required initialization - before_filter :_prepare_context + before_action :_prepare_context def hello_world @value = "Hello" diff --git a/actionpack/test/controller/render_test.rb b/actionpack/test/controller/render_test.rb index 859ed1466b..7640bc12a2 100644 --- a/actionpack/test/controller/render_test.rb +++ b/actionpack/test/controller/render_test.rb @@ -37,7 +37,7 @@ end class TestController < ActionController::Base protect_from_forgery - before_filter :set_variable_for_layout + before_action :set_variable_for_layout class LabellingFormBuilder < ActionView::Helpers::FormBuilder end @@ -137,7 +137,7 @@ class TestController < ActionController::Base def conditional_hello_with_bangs render :action => 'hello_world' end - before_filter :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs + before_action :handle_last_modified_and_etags, :only=>:conditional_hello_with_bangs def handle_last_modified_and_etags fresh_when(:last_modified => Time.now.utc.beginning_of_day, :etag => [ :foo, 123 ]) @@ -710,7 +710,7 @@ class TestController < ActionController::Base render :action => "calling_partial_with_layout", :layout => "layouts/partial_with_layout" end - before_filter :only => :render_with_filters do + before_action only: :render_with_filters do request.format = :xml end diff --git a/actionpack/test/controller/rescue_test.rb b/actionpack/test/controller/rescue_test.rb index 48e2d6491e..4898b0c57f 100644 --- a/actionpack/test/controller/rescue_test.rb +++ b/actionpack/test/controller/rescue_test.rb @@ -68,9 +68,9 @@ class RescueController < ActionController::Base render :text => 'io error' end - before_filter(:only => :before_filter_raises) { raise 'umm nice' } + before_action(only: :before_action_raises) { raise 'umm nice' } - def before_filter_raises + def before_action_raises end def raises diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb index 718d06ef38..888791b874 100644 --- a/actionpack/test/controller/show_exceptions_test.rb +++ b/actionpack/test/controller/show_exceptions_test.rb @@ -5,7 +5,7 @@ module ShowExceptions use ActionDispatch::ShowExceptions, ActionDispatch::PublicExceptions.new("#{FIXTURE_LOAD_PATH}/public") use ActionDispatch::DebugExceptions - before_filter :only => :another_boom do + before_action only: :another_boom do request.env["action_dispatch.show_detailed_exceptions"] = true end diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 40f6dc6f0f..c6e7a523b9 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -4,7 +4,7 @@ class ViewLoadPathsTest < ActionController::TestCase class TestController < ActionController::Base def self.controller_path() "test" end - before_filter :add_view_path, :only => :hello_world_at_request_time + before_action :add_view_path, only: :hello_world_at_request_time def hello_world() end def hello_world_at_request_time() render(:action => 'hello_world') end diff --git a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb index 63c5ea26a6..399f15199c 100644 --- a/actionpack/test/dispatch/request/multipart_params_parsing_test.rb +++ b/actionpack/test/dispatch/request/multipart_params_parsing_test.rb @@ -123,6 +123,18 @@ class MultipartParamsParsingTest < ActionDispatch::IntegrationTest end end + # This can happen in Internet Explorer when redirecting after multipart form submit. + test "does not raise EOFError on GET request with multipart content-type" do + with_routing do |set| + set.draw do + get ':action', to: 'multipart_params_parsing_test/test' + end + headers = { "CONTENT_TYPE" => "multipart/form-data; boundary=AaB03x" } + get "/parse", {}, headers + assert_response :ok + end + end + private def fixture(name) File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb index f2bacf3e20..263853fb6c 100644 --- a/actionpack/test/dispatch/request_test.rb +++ b/actionpack/test/dispatch/request_test.rb @@ -650,6 +650,13 @@ class RequestTest < ActiveSupport::TestCase assert_equal Mime::XML, request.negotiate_mime([Mime::XML, Mime::CSV]) end + test "raw_post rewinds rack.input if RAW_POST_DATA is nil" do + request = stub_request('rack.input' => StringIO.new("foo"), + 'CONTENT_LENGTH' => 3) + assert_equal "foo", request.raw_post + assert_equal "foo", request.env['rack.input'].read + end + test "process parameter filter" do test_hashes = [ [{'foo'=>'bar'},{'foo'=>'bar'},%w'food'], diff --git a/actionpack/test/dispatch/routing/route_set_test.rb b/actionpack/test/dispatch/routing/route_set_test.rb new file mode 100644 index 0000000000..d57b1a5637 --- /dev/null +++ b/actionpack/test/dispatch/routing/route_set_test.rb @@ -0,0 +1,86 @@ +require 'abstract_unit' + +module ActionDispatch + module Routing + class RouteSetTest < ActiveSupport::TestCase + class SimpleApp + def initialize(response) + @response = response + end + + def call(env) + [ 200, { 'Content-Type' => 'text/plain' }, [response] ] + end + end + + setup do + @set = RouteSet.new + end + + test "url helpers are added when route is added" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_raises NoMethodError do + assert_equal '/bar', url_helpers.bar_path + end + + draw do + get 'foo', to: SimpleApp.new('foo#index') + get 'bar', to: SimpleApp.new('bar#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_equal '/bar', url_helpers.bar_path + end + + test "url helpers are updated when route is updated" do + draw do + get 'bar', to: SimpleApp.new('bar#index'), as: :bar + end + + assert_equal '/bar', url_helpers.bar_path + + draw do + get 'baz', to: SimpleApp.new('baz#index'), as: :bar + end + + assert_equal '/baz', url_helpers.bar_path + end + + test "url helpers are removed when route is removed" do + draw do + get 'foo', to: SimpleApp.new('foo#index') + get 'bar', to: SimpleApp.new('bar#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_equal '/bar', url_helpers.bar_path + + draw do + get 'foo', to: SimpleApp.new('foo#index') + end + + assert_equal '/foo', url_helpers.foo_path + assert_raises NoMethodError do + assert_equal '/bar', url_helpers.bar_path + end + end + + private + def clear! + @set.clear! + end + + def draw(&block) + @set.draw(&block) + end + + def url_helpers + @set.url_helpers + end + end + end +end |