diff options
Diffstat (limited to 'actionpack/lib/action_controller')
7 files changed, 117 insertions, 44 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 3b0d094f4f..c0f10da23a 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -1,21 +1,8 @@ +require 'action_view' require "action_controller/log_subscriber" require "action_controller/metal/params_wrapper" module ActionController - # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>. - # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included, - # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that - # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly - # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of - # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt> - # beetween them to perserve the required order, we can simply do this by: - # - # ActionController::Base.superclass.send(:include, ActionView::Rendering) - # - metal = Class.new(Metal) do - include AbstractController::Rendering - end - # Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed # on request and then either it renders a template or redirects to another action. An action is defined as a public method # on the controller, which will automatically be made accessible to the web-server through \Rails Routes. @@ -99,7 +86,7 @@ module ActionController # or you can remove the entire session with +reset_session+. # # Sessions are stored by default in a browser cookie that's cryptographically signed, but unencrypted. - # This prevents the user from tampering with the session but also allows him to see its contents. + # This prevents the user from tampering with the session but also allows them to see its contents. # # Do not put secret information in cookie-based sessions! # @@ -174,7 +161,7 @@ module ActionController # render action: "overthere" # won't be called if monkeys is nil # end # - class Base < metal + class Base < Metal abstract! # We document the request and response methods here because albeit they are @@ -214,6 +201,7 @@ module ActionController end MODULES = [ + AbstractController::Rendering, AbstractController::Translation, AbstractController::AssetPaths, @@ -221,6 +209,7 @@ module ActionController HideActions, UrlFor, Redirecting, + ActionView::Layouts, Rendering, Renderers::All, ConditionalGet, @@ -261,10 +250,17 @@ module ActionController end # Define some internal variables that should not be propagated to the view. - self.protected_instance_variables = [ + PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request, - :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout - ] + :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] + + def _protected_ivars # :nodoc: + PROTECTED_IVARS + end + + def self.protected_instance_variables + PROTECTED_IVARS + end ActiveSupport.run_load_hooks(:action_controller, self) end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index b53ae7f29f..a9c3e438fb 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -73,7 +73,11 @@ module ActionController # Provides a proxy to access helpers methods from outside the view. def helpers - @helper_proxy ||= ActionView::Base.new.extend(_helpers) + @helper_proxy ||= begin + proxy = ActionView::Base.new + proxy.config = config.inheritable_copy + proxy.extend(_helpers) + end end # Overwrite modules_for_helpers to accept :all as argument, which loads diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index d3aa8f90c5..b0e164bc57 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -67,7 +67,7 @@ module ActionController private - # A hook invoked everytime a before callback is halted. + # A hook invoked every time a before callback is halted. def halted_callback_hook(filter) ActiveSupport::Notifications.instrument("halted_callback.action_controller", :filter => filter) end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index a072fce1a1..dedff9f476 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -181,13 +181,41 @@ module ActionController #:nodoc: # end # end # + # Formats can have different variants. + # + # The request variant is a specialization of the request format, like <tt>:tablet</tt>, + # <tt>:phone</tt>, or <tt>:desktop<tt>. + # + # We often want to render different html/json/xml templates for phones, + # tablets, and desktop browsers. Variants make it easy. + # + # You can set the variant in a +before_action+: + # + # request.variant = :tablet if request.user_agent =~ /iPad/ + # + # Respond to variants in the action just like you respond to formats: + # + # respond_to do |format| + # format.html do |variant| + # variant.tablet # renders app/views/projects/show.html+tablet.erb + # variant.phone { extra_setup; render ... } + # variant.none { special_setup } # executed only if there is no variant set + # end + # end + # + # Provide separate templates for each format and variant: + # + # app/views/projects/show.html.erb + # app/views/projects/show.html+tablet.erb + # app/views/projects/show.html+phone.erb + # # Be sure to check the documentation of +respond_with+ and # <tt>ActionController::MimeResponds.respond_to</tt> for more examples. def respond_to(*mimes, &block) raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given? if collector = retrieve_collector_from_mimes(mimes, &block) - response = collector.response + response = collector.response(request.variant) response ? response.call : render({}) end end @@ -260,7 +288,7 @@ module ActionController #:nodoc: # * for other requests - i.e. data formats such as xml, json, csv etc, if # the resource passed to +respond_with+ responds to <code>to_<format></code>, # the method attempts to render the resource in the requested format - # directly, e.g. for an xml request, the response is equivalent to calling + # directly, e.g. for an xml request, the response is equivalent to calling # <code>render xml: resource</code>. # # === Nested resources @@ -321,13 +349,15 @@ module ActionController #:nodoc: # 2. <tt>:action</tt> - overwrites the default render action used after an # unsuccessful html +post+ request. def respond_with(*resources, &block) - raise "In order to use respond_with, first you need to declare the formats your " \ - "controller responds to in the class level" if self.class.mimes_for_respond_to.empty? + if self.class.mimes_for_respond_to.empty? + raise "In order to use respond_with, first you need to declare the " \ + "formats your controller responds to in the class level." + end if collector = retrieve_collector_from_mimes(&block) options = resources.size == 1 ? {} : resources.extract_options! options = options.clone - options[:default_response] = collector.response + options[:default_response] = collector.response(request.variant) (options.delete(:responder) || self.class.responder).call(self, resources, options) end end @@ -396,11 +426,12 @@ module ActionController #:nodoc: # request, with this response then being accessible by calling #response. class Collector include AbstractController::Collector - attr_accessor :order, :format + attr_accessor :format def initialize(mimes) - @order, @responses = [], {} - mimes.each { |mime| send(mime) } + @responses = {} + + mimes.each { |mime| @responses["Mime::#{mime.upcase}".constantize] = nil } end def any(*args, &block) @@ -414,16 +445,54 @@ module ActionController #:nodoc: def custom(mime_type, &block) mime_type = Mime::Type.lookup(mime_type.to_s) unless mime_type.is_a?(Mime::Type) - @order << mime_type - @responses[mime_type] ||= block + @responses[mime_type] ||= if block_given? + block + else + VariantCollector.new + end end - def response - @responses.fetch(format, @responses[Mime::ALL]) + def response(variant) + response = @responses.fetch(format, @responses[Mime::ALL]) + if response.is_a?(VariantCollector) + response.variant(variant) + elsif response.nil? || response.arity == 0 + response + else + lambda { response.call VariantFilter.new(variant) } + end end def negotiate_format(request) - @format = request.negotiate_mime(order) + @format = request.negotiate_mime(@responses.keys) + end + + #Used for inline syntax + class VariantCollector #:nodoc: + def initialize + @variants = {} + end + + def method_missing(name, *args, &block) + @variants[name] = block if block_given? + end + + def variant(name) + @variants[name.nil? ? :none : name] + end + end + + #Used for nested block syntax + class VariantFilter #:nodoc: + def initialize(variant) + @variant = variant + end + + def method_missing(name) + if block_given? + yield if name == @variant || (name == :none && @variant.nil?) + end + end end end end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 90f0ef0b1c..66d34f3b67 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -27,18 +27,15 @@ module ActionController end def render_to_body(options = {}) - super || if options[:text].present? - options[:text] - else - " " - end + super || options[:text].presence || ' ' end private def _process_format(format) super - self.content_type ||= format.to_s + # format is a Mime::NullType instance here then this condition can't be changed to `if format` + self.content_type ||= format.to_s unless format.nil? end # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb index 66ff34a794..e24b56fa91 100644 --- a/actionpack/lib/action_controller/metal/responder.rb +++ b/actionpack/lib/action_controller/metal/responder.rb @@ -144,7 +144,7 @@ module ActionController #:nodoc: undef_method(:to_json) if method_defined?(:to_json) undef_method(:to_yaml) if method_defined?(:to_yaml) - # Initializes a new responder an invoke the proper format. If the format is + # Initializes a new responder and invokes the proper format. If the format is # not defined, call to_format. # def self.call(*args) @@ -270,7 +270,7 @@ module ActionController #:nodoc: resource.respond_to?(:errors) && !resource.errors.empty? end - # Check whether the neceessary Renderer is available + # Check whether the necessary Renderer is available def has_renderer? Renderers::RENDERERS.include?(format) end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 66403d533c..b4948d99a8 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -17,7 +17,7 @@ module ActionController def initialize(param) # :nodoc: @param = param - super("param not found: #{param}") + super("param is missing or the value is empty: #{param}") end end @@ -284,7 +284,14 @@ module ActionController # params.fetch(:none, 'Francesco') # => "Francesco" # params.fetch(:none) { 'Francesco' } # => "Francesco" def fetch(key, *args) - convert_hashes_to_parameters(key, super) + value = super + # Don't rely on +convert_hashes_to_parameters+ + # so as to not mutate via a +fetch+ + if value.is_a?(Hash) + value = self.class.new(value) + value.permit! if permitted? + end + value rescue KeyError raise ActionController::ParameterMissing.new(key) end |