aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/base.rb10
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb224
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb7
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb70
5 files changed, 274 insertions, 39 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index ca0dccf575..c03c77cb4a 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -206,13 +206,17 @@ module ActionController
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
+ # Before callbacks should also be executed the earliest as possible, so
+ # also include them at the bottom.
+ AbstractController::Callbacks,
+
# Add instrumentations hooks at the bottom, to ensure they instrument
# all the methods properly.
Instrumentation,
- # Before callbacks should also be executed the earliest as possible, so
- # also include them at the bottom.
- AbstractController::Callbacks,
+ # Params wrapper should come before instrumentation so they are
+ # properly showed in logs
+ ParamsWrapper,
# The same with rescue, append it at the end to wrap as much as possible.
Rescue
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index dc3ea939e6..4e54c2ad88 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -14,7 +14,7 @@ module ActionController
attr_internal :view_runtime
- def process_action(action, *args)
+ def process_action(*args)
raw_payload = {
:controller => self.class.name,
:action => self.action_name,
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
new file mode 100644
index 0000000000..21bbe17dc3
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -0,0 +1,224 @@
+require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/array/wrap'
+require 'action_dispatch/http/mime_types'
+
+module ActionController
+ # Wraps parameters hash into nested hash. This will allow client to submit
+ # POST request without having to specify a root element in it.
+ #
+ # By default this functionality won't be enabled. You can enable
+ # it globally by setting +ActionController::Base.wrap_parameters+:
+ #
+ # ActionController::Base.wrap_parameters = [:json]
+ #
+ # You could also turn it on per controller by setting the format array to
+ # non-empty array:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters :format => [:json, :xml]
+ # end
+ #
+ # If you enable +ParamsWrapper+ for +:json+ format. Instead of having to
+ # send JSON parameters like this:
+ #
+ # {"user": {"name": "Konata"}}
+ #
+ # You can now just send a parameters like this:
+ #
+ # {"name": "Konata"}
+ #
+ # And it will be wrapped into a nested hash with the key name matching
+ # controller's name. For example, if you're posting to +UsersController+,
+ # your new +params+ hash will look like this:
+ #
+ # {"name" => "Konata", "user" => {"name" => "Konata"}}
+ #
+ # You can also specify the key in which the parameters should be wrapped to,
+ # and also the list of attributes it should wrap by using either +:only+ or
+ # +:except+ options like this:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters :person, :only => [:username, :password]
+ # end
+ #
+ # If you're going to pass the parameters to an +ActiveModel+ object (such as
+ # +User.new(params[:user])+), you might consider passing the model class to
+ # the method instead. The +ParamsWrapper+ will actually try to determine the
+ # list of attribute names from the model and only wrap those attributes:
+ #
+ # class UsersController < ApplicationController
+ # wrap_parameters Person
+ # end
+ #
+ # You still could pass +:only+ and +:except+ to set the list of attributes
+ # you want to wrap.
+ #
+ # By default, if you don't specify the key in which the parameters would be
+ # wrapped to, +ParamsWrapper+ will actually try to determine if there's
+ # a model related to it or not. This controller, for example:
+ #
+ # class Admin::UsersController < ApplicationController
+ # end
+ #
+ # will try to check if +Admin::User+ or +User+ model exists, and use it to
+ # determine the wrapper key respectively. If both of the model doesn't exists,
+ # it will then fallback to use +user+ as the key.
+ module ParamsWrapper
+ extend ActiveSupport::Concern
+
+ EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+
+ included do
+ class_attribute :_wrapper_options
+ self._wrapper_options = {:format => []}
+ end
+
+ module ClassMethods
+ # Sets the name of the wrapper key, or the model which +ParamsWrapper+
+ # would use to determine the attribute names from.
+ #
+ # ==== Examples
+ # wrap_parameters :format => :xml
+ # # enables the parmeter wrapper for XML format
+ #
+ # wrap_parameters :person
+ # # wraps parameters into +params[:person]+ hash
+ #
+ # wrap_parameters Person
+ # # wraps parameters by determine the wrapper key from Person class
+ # (+person+, in this case) and the list of attribute names
+ #
+ # wrap_parameters :only => [:username, :title]
+ # # wraps only +:username+ and +:title+ attributes from parameters.
+ #
+ # wrap_parameters false
+ # # disable parameters wrapping for this controller altogether.
+ #
+ # ==== Options
+ # * <tt>:format</tt> - The list of formats in which the parameters wrapper
+ # will be enabled.
+ # * <tt>:only</tt> - The list of attribute names which parameters wrapper
+ # will wrap into a nested hash.
+ # * <tt>:except</tt> - The list of attribute names which parameters wrapper
+ # will exclude from a nested hash.
+ def wrap_parameters(name_or_model_or_options, options = {})
+ model = nil
+
+ case name_or_model_or_options
+ when Hash
+ options = name_or_model_or_options
+ when false
+ options = options.merge(:format => [])
+ when Symbol, String
+ options = options.merge(:name => name_or_model_or_options)
+ else
+ model = name_or_model_or_options
+ end
+
+ _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ end
+
+ # Sets the default wrapper key or model which will be used to determine
+ # wrapper key and attribute names. Will be called automatically when the
+ # module is inherited.
+ def inherited(klass)
+ if klass._wrapper_options[:format].present?
+ klass._set_wrapper_defaults(klass._wrapper_options)
+ end
+ super
+ end
+
+ protected
+
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ def _default_wrap_model
+ model_name = self.name.sub(/Controller$/, '').singularize
+
+ begin
+ model_klass = model_name.constantize
+ rescue NameError => e
+ unscoped_model_name = model_name.split("::", 2).last
+ break if unscoped_model_name == model_name
+ model_name = unscoped_model_name
+ end until model_klass
+
+ model_klass
+ end
+
+ def _set_wrapper_defaults(options, model=nil)
+ options = options.dup
+
+ unless options[:only] || options[:except]
+ model ||= _default_wrap_model
+ if model.respond_to?(:column_names)
+ options[:only] = model.column_names
+ end
+ end
+
+ unless options[:name]
+ model ||= _default_wrap_model
+ options[:name] = model ? model.to_s.demodulize.underscore :
+ controller_name.singularize
+ end
+
+ options[:only] = Array.wrap(options[:only]).collect(&:to_s) if options[:only]
+ options[:except] = Array.wrap(options[:except]).collect(&:to_s) if options[:except]
+ options[:format] = Array.wrap(options[:format])
+
+ self._wrapper_options = options
+ end
+ end
+
+ # Performs parameters wrapping upon the request. Will be called automatically
+ # by the metal call stack.
+ def process_action(*args)
+ if _wrapper_enabled?
+ wrapped_hash = _wrap_parameters request.request_parameters
+ wrapped_filtered_hash = _wrap_parameters request.filtered_parameters
+
+ # This will make the wrapped hash accessible from controller and view
+ request.parameters.merge! wrapped_hash
+ request.request_parameters.merge! wrapped_hash
+
+ # This will make the wrapped hash displayed in the log file
+ request.filtered_parameters.merge! wrapped_filtered_hash
+ end
+ super
+ end
+
+ private
+
+ # Returns the wrapper key which will use to stored wrapped parameters.
+ def _wrapper_key
+ _wrapper_options[:name]
+ end
+
+ # Returns the list of enabled formats.
+ def _wrapper_formats
+ _wrapper_options[:format]
+ end
+
+ # Returns the list of parameters which will be selected for wrapped.
+ def _wrap_parameters(parameters)
+ value = if only = _wrapper_options[:only]
+ parameters.slice(*only)
+ else
+ except = _wrapper_options[:except] || []
+ parameters.except(*(except + EXCLUDE_PARAMETERS))
+ end
+
+ { _wrapper_key => value }
+ end
+
+ # Checks if we should perform parameters wrapping.
+ def _wrapper_enabled?
+ ref = request.content_mime_type.try(:ref)
+ _wrapper_formats.include?(ref) && !request.request_parameters[_wrapper_key]
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 59a3621f72..ebadb29ea7 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -68,7 +68,7 @@ module ActionController #:nodoc:
# respond_with(@project, @task)
# end
#
- # Giving an array of resources, you ensure that the responder will redirect to
+ # Giving several resources ensures that the responder will redirect to
# <code>project_task_url</code> instead of <code>task_url</code>.
#
# Namespaced and singleton resources require a symbol to be given, as in
@@ -77,6 +77,11 @@ module ActionController #:nodoc:
#
# respond_with(@project, :manager, @task)
#
+ # Note that if you give an array, it will be treated as a collection,
+ # so the following is not equivalent:
+ #
+ # respond_with [@project, :manager, @task]
+ #
# === Custom options
#
# <code>respond_with</code> also allow you to pass options that are forwarded
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 0dd847f967..3892a12407 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -2,17 +2,16 @@ require 'active_support/core_ext/file/path'
require 'rack/chunked'
module ActionController #:nodoc:
- # Allow views to be streamed back to the client as they are rendered.
+ # Allows views to be streamed back to the client as they are rendered.
#
# The default way Rails renders views is by first rendering the template
- # and then the layout. The first chunk of response is sent to the client
- # just after the whole template is rendered, all queries are made and the
- # layout is processed.
+ # and then the layout. The response is sent to the client after the whole
+ # template is rendered, all queries are made, and the layout is processed.
#
# Streaming inverts the rendering flow by rendering the layout first and
# streaming each part of the layout as they are processed. This allows the
- # header of the html (which is usually in the layout) to be streamed back
- # to client very quickly, allowing javascripts and stylesheets to be loaded
+ # header of the HTML (which is usually in the layout) to be streamed back
+ # to client very quickly, allowing JavaScripts and stylesheets to be loaded
# earlier than usual.
#
# This approach was introduced in Rails 3.1 and is still improving. Several
@@ -20,13 +19,13 @@ module ActionController #:nodoc:
# Those points are going to be addressed soon.
#
# In order to use streaming, you will need to use a Ruby version that
- # supports Fibers (Fibers are supported since version 1.9.2 of the main
+ # supports fibers (fibers are supported since version 1.9.2 of the main
# Ruby implementation).
#
# == Examples
#
# Streaming can be added to a controller easily, all you need to do is
- # call stream at the controller class:
+ # call +stream+ in the controller class:
#
# class PostsController
# stream
@@ -42,19 +41,19 @@ module ActionController #:nodoc:
#
# class PostsController
# def index
- # @post = Post.scoped
+ # @posts = Post.scoped
# render :stream => true
# end
# end
#
# == When to use streaming
#
- # Streaming may be considering an overkill for common actions like
- # new or edit. The real benefit of streaming is on expensive actions
- # that, for example, does a lot of queries on the database.
+ # Streaming may be considered to be overkill for lightweight actions like
+ # +new+ or +edit+. The real benefit of streaming is on expensive actions
+ # that, for example, do a lot of queries on the database.
#
# In such actions, you want to delay queries execution as much as you can.
- # For example, imagine the following dashboard action:
+ # For example, imagine the following +dashboard+ action:
#
# def dashboard
# @posts = Post.all
@@ -63,10 +62,10 @@ module ActionController #:nodoc:
# end
#
# Most of the queries here are happening in the controller. In order to benefit
- # most of streaming, you would want to rewrite it as:
+ # from streaming you would want to rewrite it as:
#
# def dashboard
- # # Allow lazily execution of the query
+ # # Allow lazy execution of the queries
# @posts = Post.scoped
# @pages = Page.scoped
# @articles = Article.scoped
@@ -75,12 +74,15 @@ module ActionController #:nodoc:
#
# == Communication between layout and template
#
- # When streaming, the layout is rendered first than the template.
- # This means that, if your application currently rely on variables set
- # in the template to be used in the layout, they won't work once you
- # move to streaming. The proper way to communicate between layout and
- # template, regardless if you use streaming or not, is by using
- # +content_for+, +provide+ and +yield+.
+ # When streaming, rendering happens top-down instead of inside-out.
+ # Rails starts with the layout, and the template is rendered later,
+ # when its +yield+ is reached.
+ #
+ # This means that, if your application currently relies on instance
+ # variables set in the template to be used in the layout, they won't
+ # work once you move to streaming. The proper way to communicate
+ # between layout and template, regardless of whether you use streaming
+ # or not, is by using +content_for+, +provide+ and +yield+.
#
# Take a simple example where the layout expects the template to tell
# which title to use:
@@ -121,16 +123,16 @@ module ActionController #:nodoc:
# and you want to use streaming, you would have to render the whole template
# (and eventually trigger all queries) before streaming the title and all
# assets, which kills the purpose of streaming. For this reason Rails 3.1
- # introduces a helper called +provide+ that does the same as +content_for+
+ # introduces a new helper called +provide+ that does the same as +content_for+
# but tells the layout to stop searching for other entries and continue rendering.
#
- # For instance, the template below, using +provide+:
+ # For instance, the template above using +provide+ would be:
#
# <%= provide :title, "Main" %>
# Hello
# <%= content_for :title, " page" %>
#
- # Has as final result:
+ # Giving:
#
# <html>
# <head><title>Main</title></head>
@@ -138,27 +140,27 @@ module ActionController #:nodoc:
# </html>
#
# That said, when streaming, you need to properly check your templates
- # and chose when to use +provide+ and +content_for+.
+ # and choose when to use +provide+ and +content_for+.
#
# == Headers, cookies, session and flash
#
# When streaming, the HTTP headers are sent to the client right before
# it renders the first line. This means that, modifying headers, cookies,
- # session or flash after the template start rendering will not propagate
+ # session or flash after the template starts rendering will not propagate
# to the client.
#
- # If you try to modify cookies, session or flash, a ClosedError will be
- # raised, showing those objects are closed for modification.
+ # If you try to modify cookies, session or flash, an +ActionDispatch::ClosedError+
+ # will be raised, showing those objects are closed for modification.
#
# == Middlewares
#
# Middlewares that need to manipulate the body won't work with streaming.
# You should disable those middlewares whenever streaming in development
- # or production. For instance, Rack::Bug won't work when streaming as it
+ # or production. For instance, +Rack::Bug+ won't work when streaming as it
# needs to inject contents in the HTML body.
#
- # Also Rack::Cache won't work with streaming as it does not support
- # streaming bodies yet. So, whenever streaming, Cache-Control is automatically
+ # Also +Rack::Cache+ won't work with streaming as it does not support
+ # streaming bodies yet. Whenever streaming Cache-Control is automatically
# set to "no-cache".
#
# == Errors
@@ -193,10 +195,10 @@ module ActionController #:nodoc:
#
# unicorn_rails --config-file unicorn.config.rb
#
- # You may also want to configure other parameters like :tcp_nodelay. Please
- # check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
+ # You may also want to configure other parameters like <tt>:tcp_nodelay</tt>.
+ # Please check its documentation for more information: http://unicorn.bogomips.org/Unicorn/Configurator.html#method-i-listen
#
- # If you are using unicorn with nginx, you may need to tweak nginx.
+ # If you are using Unicorn with Nginx, you may need to tweak Nginx.
# Streaming should work out of the box on Rainbows.
#
# ==== Passenger