diff options
Diffstat (limited to 'actionpack/lib/action_controller/new_base')
15 files changed, 1047 insertions, 152 deletions
diff --git a/actionpack/lib/action_controller/new_base/base.rb b/actionpack/lib/action_controller/new_base/base.rb index 08e7a1a0e7..d7b65d37fa 100644 --- a/actionpack/lib/action_controller/new_base/base.rb +++ b/actionpack/lib/action_controller/new_base/base.rb @@ -1,60 +1,173 @@ module ActionController - class AbstractBase < AbstractController::Base - - # :api: public - attr_internal :request, :response, :params - - # :api: public - def self.controller_name - @controller_name ||= controller_path.split("/").last - end - - # :api: public - def controller_name() self.class.controller_name end - - # :api: public - def self.controller_path - @controller_path ||= self.name.sub(/Controller$/, '').underscore - end - - # :api: public - def controller_path() self.class.controller_path end - - # :api: private - def self.action_methods - @action_names ||= Set.new(self.public_instance_methods - self::CORE_METHODS) - end - - # :api: private - def self.action_names() action_methods end - - # :api: private - def action_methods() self.class.action_names end - - # :api: private - def action_names() action_methods end - - # :api: plugin - def self.call(env) - controller = new - controller.call(env).to_rack - end - - # :api: plugin - def response_body=(body) - @_response.body = body - end - - # :api: private - def call(env) - @_request = ActionDispatch::Request.new(env) - @_response = ActionDispatch::Response.new - process(@_request.parameters[:action]) - end - - # :api: private - def to_rack - response.to_a + class Base < Http + abstract! + + include AbstractController::Benchmarker + include AbstractController::Callbacks + include AbstractController::Logger + + include ActionController::Helpers + include ActionController::HideActions + include ActionController::UrlFor + include ActionController::Redirector + include ActionController::Renderer + include ActionController::Renderers::All + include ActionController::Layouts + include ActionController::ConditionalGet + include ActionController::RackConvenience + + # Legacy modules + include SessionManagement + include ActionDispatch::StatusCodes + include ActionController::Caching + include ActionController::MimeResponds + + # Rails 2.x compatibility + include ActionController::Rails2Compatibility + + include ActionController::Cookies + include ActionController::Session + include ActionController::Flash + include ActionController::Verification + include ActionController::RequestForgeryProtection + include ActionController::Streaming + include ActionController::HttpAuthentication::Basic::ControllerMethods + include ActionController::HttpAuthentication::Digest::ControllerMethods + include ActionController::FilterParameterLogging + include ActionController::Translation + + # TODO: Extract into its own module + # This should be moved together with other normalizing behavior + module ImplicitRender + def send_action(method_name) + ret = super + default_render unless performed? + ret + end + + def default_render + render + end + + def method_for_action(action_name) + super || begin + if view_paths.find_by_parts?(action_name.to_s, {:formats => formats, :locales => [I18n.locale]}, controller_path) + "default_render" + end + end + end + end + + include ImplicitRender + + include ActionController::Rescue + + def self.inherited(klass) + ::ActionController::Base.subclasses << klass.to_s + super + end + + def self.subclasses + @subclasses ||= [] + end + + def self.app_loaded! + @subclasses.each do |subclass| + subclass.constantize._write_layout_method + end + end + + def _normalize_options(action = nil, options = {}, &blk) + if action.is_a?(Hash) + options, action = action, nil + elsif action.is_a?(String) || action.is_a?(Symbol) + key = case action = action.to_s + when %r{^/} then :file + when %r{/} then :template + else :action + end + options.merge! key => action + elsif action + options.merge! :partial => action + end + + if options.key?(:action) && options[:action].to_s.index("/") + options[:template] = options.delete(:action) + end + + if options[:status] + options[:status] = interpret_status(options[:status]).to_i + end + + options[:update] = blk if block_given? + options + end + + def render(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + def render_to_string(action = nil, options = {}, &blk) + options = _normalize_options(action, options, &blk) + super(options) + end + + # Redirects the browser to the target specified in +options+. This parameter can take one of three forms: + # + # * <tt>Hash</tt> - The URL will be generated by calling url_for with the +options+. + # * <tt>Record</tt> - The URL will be generated by calling url_for with the +options+, which will reference a named URL for that record. + # * <tt>String</tt> starting with <tt>protocol://</tt> (like <tt>http://</tt>) - Is passed straight through as the target for redirection. + # * <tt>String</tt> not containing a protocol - The current protocol and host is prepended to the string. + # * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places. + # Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt> + # + # Examples: + # redirect_to :action => "show", :id => 5 + # redirect_to post + # redirect_to "http://www.rubyonrails.org" + # redirect_to "/images/screenshot.jpg" + # redirect_to articles_url + # redirect_to :back + # + # The redirection happens as a "302 Moved" header unless otherwise specified. + # + # Examples: + # redirect_to post_url(@post), :status=>:found + # redirect_to :action=>'atom', :status=>:moved_permanently + # redirect_to post_url(@post), :status=>301 + # redirect_to :action=>'atom', :status=>302 + # + # When using <tt>redirect_to :back</tt>, if there is no referrer, + # RedirectBackError will be raised. You may specify some fallback + # behavior for this case by rescuing RedirectBackError. + def redirect_to(options = {}, response_status = {}) #:doc: + raise ActionControllerError.new("Cannot redirect to nil!") if options.nil? + + status = if options.is_a?(Hash) && options.key?(:status) + interpret_status(options.delete(:status)) + elsif response_status.key?(:status) + interpret_status(response_status[:status]) + else + 302 + end + + url = case options + # The scheme name consist of a letter followed by any combination of + # letters, digits, and the plus ("+"), period ("."), or hyphen ("-") + # characters; and is terminated by a colon (":"). + when %r{^\w[\w\d+.-]*:.*} + options + when String + request.protocol + request.host_with_port + options + when :back + raise RedirectBackError unless refer = request.headers["Referer"] + refer + else + url_for(options) + end + + super(url, status) end end end diff --git a/actionpack/lib/action_controller/new_base/compatibility.rb b/actionpack/lib/action_controller/new_base/compatibility.rb new file mode 100644 index 0000000000..f278c2da14 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/compatibility.rb @@ -0,0 +1,138 @@ +module ActionController + module Rails2Compatibility + extend ActiveSupport::Concern + + class ::ActionController::ActionControllerError < StandardError #:nodoc: + end + + # Temporary hax + included do + ::ActionController::UnknownAction = ::AbstractController::ActionNotFound + ::ActionController::DoubleRenderError = ::AbstractController::DoubleRenderError + + cattr_accessor :session_options + self.session_options = {} + + cattr_accessor :allow_concurrency + self.allow_concurrency = false + + cattr_accessor :param_parsers + self.param_parsers = { Mime::MULTIPART_FORM => :multipart_form, + Mime::URL_ENCODED_FORM => :url_encoded_form, + Mime::XML => :xml_simple, + Mime::JSON => :json } + + cattr_accessor :relative_url_root + self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] + + cattr_accessor :default_charset + self.default_charset = "utf-8" + + # cattr_reader :protected_instance_variables + cattr_accessor :protected_instance_variables + self.protected_instance_variables = %w(@assigns @performed_redirect @performed_render @variables_added @request_origin @url @parent_controller + @action_name @before_filter_chain_aborted @action_cache_path @_headers @_params + @_flash @_response) + + # Indicates whether or not optimise the generated named + # route helper methods + cattr_accessor :optimise_named_routes + self.optimise_named_routes = true + + cattr_accessor :resources_path_names + self.resources_path_names = { :new => 'new', :edit => 'edit' } + + # Controls the resource action separator + cattr_accessor :resource_action_separator + self.resource_action_separator = "/" + + cattr_accessor :use_accept_header + self.use_accept_header = true + + cattr_accessor :page_cache_directory + self.page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : "" + + cattr_reader :cache_store + + cattr_accessor :consider_all_requests_local + self.consider_all_requests_local = true + + # Prepends all the URL-generating helpers from AssetHelper. This makes it possible to easily move javascripts, stylesheets, + # and images to a dedicated asset server away from the main web server. Example: + # ActionController::Base.asset_host = "http://assets.example.com" + cattr_accessor :asset_host + + cattr_accessor :ip_spoofing_check + self.ip_spoofing_check = true + end + + # For old tests + def initialize_template_class(*) end + def assign_shortcuts(*) end + + # TODO: Remove this after we flip + def template + @template ||= _action_view + end + + def process_action(*) + template + super + end + + module ClassMethods + def consider_all_requests_local + end + + def rescue_action(env) + raise env["action_dispatch.rescue.exception"] + end + + # Defines the storage option for cached fragments + def cache_store=(store_option) + @@cache_store = ActiveSupport::Cache.lookup_store(store_option) + end + end + + def render_to_body(options) + if options.is_a?(Hash) && options.key?(:template) + options[:template].sub!(/^\//, '') + end + + options[:text] = nil if options[:nothing] == true + + body = super + body = [' '] if body.blank? + body + end + + def _handle_method_missing + method_missing(@_action_name.to_sym) + end + + def method_for_action(action_name) + super || (respond_to?(:method_missing) && "_handle_method_missing") + end + + def _layout_prefix(name) + super unless name =~ /\blayouts/ + end + + def performed? + response_body + end + + # ==== Request only view path switching ==== + def append_view_path(path) + view_paths.push(*path) + end + + def prepend_view_path(path) + view_paths.unshift(*path) + end + + def view_paths + _action_view.view_paths + end + end +end diff --git a/actionpack/lib/action_controller/new_base/conditional_get.rb b/actionpack/lib/action_controller/new_base/conditional_get.rb new file mode 100644 index 0000000000..8bd6db500b --- /dev/null +++ b/actionpack/lib/action_controller/new_base/conditional_get.rb @@ -0,0 +1,133 @@ +module ActionController + module ConditionalGet + extend ActiveSupport::Concern + + depends_on RackConvenience + + # Sets the etag, last_modified, or both on the response and renders a + # "304 Not Modified" response if the request is already fresh. + # + # Parameters: + # * <tt>:etag</tt> + # * <tt>:last_modified</tt> + # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # + # Example: + # + # def show + # @article = Article.find(params[:id]) + # fresh_when(:etag => @article, :last_modified => @article.created_at.utc, :public => true) + # end + # + # This will render the show template if the request isn't sending a matching etag or + # If-Modified-Since header and just a "304 Not Modified" response if there's a match. + # + def fresh_when(options) + options.assert_valid_keys(:etag, :last_modified, :public) + + response.etag = options[:etag] if options[:etag] + response.last_modified = options[:last_modified] if options[:last_modified] + + if options[:public] + cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + cache_control.delete("private") + cache_control.delete("no-cache") + cache_control << "public" + response.headers["Cache-Control"] = cache_control.join(', ') + end + + if request.fresh?(response) + head :not_modified + end + end + + # Return a response that has no content (merely headers). The options + # argument is interpreted to be a hash of header names and values. + # This allows you to easily return a response that consists only of + # significant headers: + # + # head :created, :location => person_path(@person) + # + # It can also be used to return exceptional conditions: + # + # return head(:method_not_allowed) unless request.post? + # return head(:bad_request) unless valid_request? + # render + def head(*args) + if args.length > 2 + raise ArgumentError, "too many arguments to head" + elsif args.empty? + raise ArgumentError, "too few arguments to head" + end + options = args.extract_options! + status = args.shift || options.delete(:status) || :ok + + options.each do |key, value| + headers[key.to_s.dasherize.split(/-/).map { |v| v.capitalize }.join("-")] = value.to_s + end + + render :nothing => true, :status => status + end + + # Sets the etag and/or last_modified on the response and checks it against + # the client request. If the request doesn't match the options provided, the + # request is considered stale and should be generated from scratch. Otherwise, + # it's fresh and we don't need to generate anything and a reply of "304 Not Modified" is sent. + # + # Parameters: + # * <tt>:etag</tt> + # * <tt>:last_modified</tt> + # * <tt>:public</tt> By default the Cache-Control header is private, set this to true if you want your application to be cachable by other devices (proxy caches). + # + # Example: + # + # def show + # @article = Article.find(params[:id]) + # + # if stale?(:etag => @article, :last_modified => @article.created_at.utc) + # @statistics = @article.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + def stale?(options) + fresh_when(options) + !request.fresh?(response) + end + + # Sets a HTTP 1.1 Cache-Control header. Defaults to issuing a "private" instruction, so that + # intermediate caches shouldn't cache the response. + # + # Examples: + # expires_in 20.minutes + # expires_in 3.hours, :public => true + # expires in 3.hours, 'max-stale' => 5.hours, :public => true + # + # This method will overwrite an existing Cache-Control header. + # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html for more possibilities. + def expires_in(seconds, options = {}) #:doc: + cache_control = response.headers["Cache-Control"].split(",").map {|k| k.strip } + + cache_control << "max-age=#{seconds}" + cache_control.delete("no-cache") + if options[:public] + cache_control.delete("private") + cache_control << "public" + else + cache_control << "private" + end + + # This allows for additional headers to be passed through like 'max-stale' => 5.hours + cache_control += options.symbolize_keys.reject{|k,v| k == :public || k == :private }.map{ |k,v| v == true ? k.to_s : "#{k.to_s}=#{v.to_s}"} + + response.headers["Cache-Control"] = cache_control.join(', ') + end + + # Sets a HTTP 1.1 Cache-Control header of "no-cache" so no caching should occur by the browser or + # intermediate caches (like caching proxy servers). + def expires_now #:doc: + response.headers["Cache-Control"] = "no-cache" + end + end +end diff --git a/actionpack/lib/action_controller/new_base/helpers.rb b/actionpack/lib/action_controller/new_base/helpers.rb new file mode 100644 index 0000000000..c2ebd343e3 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/helpers.rb @@ -0,0 +1,129 @@ +require 'active_support/core_ext/load_error' +require 'active_support/core_ext/name_error' +require 'active_support/dependencies' + +module ActionController + module Helpers + extend ActiveSupport::Concern + + depends_on AbstractController::Helpers + + included do + # Set the default directory for helpers + class_inheritable_accessor :helpers_dir + self.helpers_dir = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") + end + + module ClassMethods + def inherited(klass) + klass.__send__ :default_helper_module! + super + end + + # The +helper+ class method can take a series of helper module names, a block, or both. + # + # * <tt>*args</tt>: One or more modules, strings or symbols, or the special symbol <tt>:all</tt>. + # * <tt>&block</tt>: A block defining helper methods. + # + # ==== Examples + # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file + # and include the module in the template class. The second form illustrates how to include custom helpers + # when working with namespaced controllers, or other cases where the file containing the helper definition is not + # in one of Rails' standard load paths: + # helper :foo # => requires 'foo_helper' and includes FooHelper + # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper + # + # When the argument is a module it will be included directly in the template class. + # helper FooHelper # => includes FooHelper + # + # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers beneath + # <tt>ActionController::Base.helpers_dir</tt> (defaults to <tt>app/helpers/**/*.rb</tt> under RAILS_ROOT). + # helper :all + # + # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available + # to the template. + # # One line + # helper { def hello() "Hello, world!" end } + # # Multi-line + # helper do + # def foo(bar) + # "#{bar} is the very best" + # end + # end + # + # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of + # +symbols+, +strings+, +modules+ and blocks. + # helper(:three, BlindHelper) { def mice() 'mice' end } + # + def helper(*args, &block) + args.flatten.each do |arg| + case arg + when :all + helper all_application_helpers + when String, Symbol + file_name = arg.to_s.underscore + '_helper' + class_name = file_name.camelize + + begin + require_dependency(file_name) + rescue LoadError => load_error + requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] + if requiree == file_name + msg = "Missing helper file helpers/#{file_name}.rb" + raise LoadError.new(msg).copy_blame!(load_error) + else + raise + end + end + + super class_name.constantize + else + super args + end + end + + # Evaluate block in template class if given. + master_helper_module.module_eval(&block) if block_given? + end + + # Declares helper accessors for controller attributes. For example, the + # following adds new +name+ and <tt>name=</tt> instance methods to a + # controller and makes them available to the view: + # helper_attr :name + # attr_accessor :name + def helper_attr(*attrs) + attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } + end + + # Provides a proxy to access helpers methods from outside the view. + def helpers + unless @helper_proxy + @helper_proxy = ActionView::Base.new + @helper_proxy.extend master_helper_module + else + @helper_proxy + end + end + + private + def default_helper_module! + unless name.blank? + module_name = name.sub(/Controller$|$/, 'Helper') + module_path = module_name.split('::').map { |m| m.underscore }.join('/') + require_dependency module_path + helper module_name.constantize + end + rescue MissingSourceFile => e + raise unless e.is_missing? module_path + rescue NameError => e + raise unless e.missing_name? module_name + end + + # Extract helper names from files in app/helpers/**/*.rb + def all_application_helpers + extract = /^#{Regexp.quote(helpers_dir)}\/?(.*)_helper.rb$/ + Dir["#{helpers_dir}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } + end + end + end +end diff --git a/actionpack/lib/action_controller/new_base/hide_actions.rb b/actionpack/lib/action_controller/new_base/hide_actions.rb index 473a8ea72b..b45e520bee 100644 --- a/actionpack/lib/action_controller/new_base/hide_actions.rb +++ b/actionpack/lib/action_controller/new_base/hide_actions.rb @@ -1,31 +1,39 @@ module ActionController module HideActions - setup do + extend ActiveSupport::Concern + + included do extlib_inheritable_accessor :hidden_actions - self.hidden_actions ||= Set.new + self.hidden_actions ||= Set.new end - - def action_methods() self.class.action_names end - def action_names() action_methods end - - private - - def respond_to_action?(action_name) - !hidden_actions.include?(action_name) && (super || respond_to?(:method_missing)) + + def action_methods + self.class.action_names end - - module ClassMethods - def hide_action(*args) - args.each do |arg| - self.hidden_actions << arg.to_s - end - end - - def action_methods - @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)}) - end - def self.action_names() action_methods end + def action_names + action_methods end + + private + def action_method?(action_name) + !hidden_actions.include?(action_name) && super + end + + module ClassMethods + def hide_action(*args) + args.each do |arg| + self.hidden_actions << arg.to_s + end + end + + def action_methods + @action_names ||= Set.new(super.reject {|name| self.hidden_actions.include?(name.to_s)}) + end + + def self.action_names + action_methods + end + end end -end
\ No newline at end of file +end diff --git a/actionpack/lib/action_controller/new_base/http.rb b/actionpack/lib/action_controller/new_base/http.rb new file mode 100644 index 0000000000..c96aaaa865 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/http.rb @@ -0,0 +1,91 @@ +require 'action_controller/abstract' +require 'active_support/core_ext/module/delegation' + +module ActionController + class Http < AbstractController::Base + abstract! + + # :api: public + attr_internal :params, :env + + # :api: public + def self.controller_name + @controller_name ||= controller_path.split("/").last + end + + # :api: public + def controller_name + self.class.controller_name + end + + # :api: public + def self.controller_path + @controller_path ||= self.name.sub(/Controller$/, '').underscore + end + + # :api: public + def controller_path + self.class.controller_path + end + + # :api: private + def self.action_names + action_methods + end + + # :api: private + def action_names + action_methods + end + + # :api: plugin + def self.call(env) + controller = new + controller.call(env).to_rack + end + + # The details below can be overridden to support a specific + # Request and Response object. The default ActionController::Base + # implementation includes RackConvenience, which makes a request + # and response object available. You might wish to control the + # environment and response manually for performance reasons. + + attr_internal :status, :headers, :content_type + + def initialize(*) + @_headers = {} + super + end + + # Basic implements for content_type=, location=, and headers are + # provided to reduce the dependency on the RackConvenience module + # in Renderer and Redirector. + + def content_type=(type) + headers["Content-Type"] = type.to_s + end + + def location=(url) + headers["Location"] = url + end + + # :api: private + def call(name, env) + @_env = env + process(name) + to_rack + end + + # :api: private + def to_rack + [status, headers, response_body] + end + + def self.action(name) + @actions ||= {} + @actions[name.to_s] ||= proc do |env| + new.call(name, env) + end + end + end +end diff --git a/actionpack/lib/action_controller/new_base/layouts.rb b/actionpack/lib/action_controller/new_base/layouts.rb index a8e0809ac6..727358c394 100644 --- a/actionpack/lib/action_controller/new_base/layouts.rb +++ b/actionpack/lib/action_controller/new_base/layouts.rb @@ -1,37 +1,34 @@ module ActionController module Layouts + extend ActiveSupport::Concern + depends_on ActionController::Renderer depends_on AbstractController::Layouts - + module ClassMethods def _implied_layout_name controller_path end end - - def render_to_body(options) - # render :text => ..., :layout => ... - # or - # render :anything_else - if !options.key?(:text) || options.key?(:layout) - options[:_layout] = options.key?(:layout) ? _layout_for_option(options[:layout]) : _default_layout + + private + def _determine_template(options) + super + if (!options.key?(:text) && !options.key?(:inline) && !options.key?(:partial)) || options.key?(:layout) + options[:_layout] = _layout_for_option(options.key?(:layout) ? options[:layout] : :none, options[:_template].details) + end end - - super - end - - private - - def _layout_for_option(name) - case name - when String then _layout_for_name(name) - when true then _default_layout(true) - when false, nil then nil - else - raise ArgumentError, - "String, true, or false, expected for `layout'; you passed #{name.inspect}" + + def _layout_for_option(name, details) + case name + when String then _layout_for_name(name, details) + when true then _default_layout(true, details) + when :none then _default_layout(false, details) + when false, nil then nil + else + raise ArgumentError, + "String, true, or false, expected for `layout'; you passed #{name.inspect}" + end end - end - end end diff --git a/actionpack/lib/action_controller/new_base/rack_convenience.rb b/actionpack/lib/action_controller/new_base/rack_convenience.rb new file mode 100644 index 0000000000..5dfa7d12f3 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/rack_convenience.rb @@ -0,0 +1,33 @@ +module ActionController + module RackConvenience + extend ActiveSupport::Concern + + included do + delegate :headers, :status=, :location=, + :status, :location, :content_type, :to => "@_response" + attr_internal :request, :response + end + + def call(name, env) + @_request = ActionDispatch::Request.new(env) + @_response = ActionDispatch::Response.new + @_response.request = request + super + end + + def params + @_params ||= @_request.parameters + end + + # :api: private + def to_rack + @_response.prepare! + @_response.to_a + end + + def response_body=(body) + response.body = body if response + super + end + end +end diff --git a/actionpack/lib/action_controller/new_base/redirector.rb b/actionpack/lib/action_controller/new_base/redirector.rb new file mode 100644 index 0000000000..20060b001f --- /dev/null +++ b/actionpack/lib/action_controller/new_base/redirector.rb @@ -0,0 +1,19 @@ +module ActionController + class RedirectBackError < AbstractController::Error #:nodoc: + DEFAULT_MESSAGE = 'No HTTP_REFERER was set in the request to this action, so redirect_to :back could not be called successfully. If this is a test, make sure to specify request.env["HTTP_REFERER"].' + + def initialize(message = nil) + super(message || DEFAULT_MESSAGE) + end + end + + module Redirector + def redirect_to(url, status) #:doc: + raise AbstractController::DoubleRenderError if response_body + logger.info("Redirected to #{url}") if logger && logger.info? + self.status = status + self.location = url.gsub(/[\r\n]/, '') + self.response_body = "<html><body>You are being <a href=\"#{CGI.escapeHTML(url)}\">redirected</a>.</body></html>" + end + end +end diff --git a/actionpack/lib/action_controller/new_base/render_options.rb b/actionpack/lib/action_controller/new_base/render_options.rb new file mode 100644 index 0000000000..04b954134f --- /dev/null +++ b/actionpack/lib/action_controller/new_base/render_options.rb @@ -0,0 +1,103 @@ +module ActionController + module RenderOptions + extend ActiveSupport::Concern + + included do + extlib_inheritable_accessor :_renderers + self._renderers = [] + end + + module ClassMethods + def _write_render_options + renderers = _renderers.map do |r| + <<-RUBY_EVAL + if options.key?(:#{r}) + _process_options(options) + return _render_#{r}(options[:#{r}], options) + end + RUBY_EVAL + end + + class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 + def _handle_render_options(options) + #{renderers.join} + end + RUBY_EVAL + end + + def _add_render_option(name) + _renderers << name + _write_render_options + end + end + + def render_to_body(options) + _handle_render_options(options) || super + end + end + + module RenderOption #:nodoc: + def self.extended(base) + base.extend ActiveSupport::Concern + base.depends_on ::ActionController::RenderOptions + + def base.register_renderer(name) + included { _add_render_option(name) } + end + end + end + + module Renderers + module Json + extend RenderOption + register_renderer :json + + def _render_json(json, options) + json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str) + json = "#{options[:callback]}(#{json})" unless options[:callback].blank? + self.content_type ||= Mime::JSON + self.response_body = json + end + end + + module Js + extend RenderOption + register_renderer :js + + def _render_js(js, options) + self.content_type ||= Mime::JS + self.response_body = js + end + end + + module Xml + extend RenderOption + register_renderer :xml + + def _render_xml(xml, options) + self.content_type ||= Mime::XML + self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml : xml + end + end + + module RJS + extend RenderOption + register_renderer :update + + def _render_update(proc, options) + generator = ActionView::Helpers::PrototypeHelper::JavaScriptGenerator.new(_action_view, &proc) + self.content_type = Mime::JS + self.response_body = generator.to_s + end + end + + module All + extend ActiveSupport::Concern + + depends_on ActionController::Renderers::Json + depends_on ActionController::Renderers::Js + depends_on ActionController::Renderers::Xml + depends_on ActionController::Renderers::RJS + end + end +end diff --git a/actionpack/lib/action_controller/new_base/renderer.rb b/actionpack/lib/action_controller/new_base/renderer.rb index ed34c46aed..e132d4fdbb 100644 --- a/actionpack/lib/action_controller/new_base/renderer.rb +++ b/actionpack/lib/action_controller/new_base/renderer.rb @@ -1,62 +1,78 @@ module ActionController module Renderer + extend ActiveSupport::Concern + depends_on AbstractController::Renderer - - def initialize(*) - self.formats = [:html] + + def process_action(*) + self.formats = request.formats.map {|x| x.to_sym} super end - - def render(action, options = {}) - # TODO: Move this into #render_to_body - if action.is_a?(Hash) - options, action = action, nil - else - options.merge! :action => action + + def render(options) + super + options[:_template] ||= _action_view._partial + self.content_type ||= begin + mime = options[:_template].mime_type + formats.include?(mime && mime.to_sym) || formats.include?(:all) ? mime : Mime::Type.lookup_by_extension(formats.first) end - - _process_options(options) - - self.response_body = render_to_body(options) + response_body end def render_to_body(options) - unless options.is_a?(Hash) - options = {:action => options} - end + _process_options(options) - if options.key?(:text) - options[:_template] = ActionView::TextTemplate.new(_text(options)) - template = nil - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:action) - options[:_template_name] = options[:action].to_s - options[:_prefix] = _prefix + if options.key?(:partial) + _render_partial(options[:partial], options) end - - super(options) + + super end - - private - - def _prefix - controller_path - end - - def _text(options) - text = options[:text] - - case text - when nil then " " - else text.to_s + + private + def _prefix + controller_path end - end - - def _process_options(options) - if status = options[:status] - response.status = status.to_i + + def _determine_template(options) + if options.key?(:text) + options[:_template] = ActionView::TextTemplate.new(options[:text], formats.first) + elsif options.key?(:inline) + handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") + template = ActionView::Template.new(options[:inline], "inline #{options[:inline].inspect}", handler, {}) + options[:_template] = template + elsif options.key?(:template) + options[:_template_name] = options[:template] + elsif options.key?(:file) + options[:_template_name] = options[:file] + elsif !options.key?(:partial) + options[:_template_name] = (options[:action] || action_name).to_s + options[:_prefix] = _prefix + end + + super + end + + def _render_partial(partial, options) + case partial + when true + options[:_prefix] = _prefix + when String + options[:_prefix] = _prefix unless partial.index('/') + options[:_template_name] = partial + else + options[:_partial_object] = true + return + end + + options[:_partial] = options[:object] || true + end + + def _process_options(options) + status, content_type, location = options.values_at(:status, :content_type, :location) + self.status = status if status + self.content_type = content_type if content_type + self.headers["Location"] = url_for(location) if location end - end end end diff --git a/actionpack/lib/action_controller/new_base/rescuable.rb b/actionpack/lib/action_controller/new_base/rescuable.rb new file mode 100644 index 0000000000..029e643d93 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/rescuable.rb @@ -0,0 +1,52 @@ +module ActionController #:nodoc: + # Actions that fail to perform as expected throw exceptions. These + # exceptions can either be rescued for the public view (with a nice + # user-friendly explanation) or for the developers view (with tons of + # debugging information). The developers view is already implemented by + # the Action Controller, but the public view should be tailored to your + # specific application. + # + # The default behavior for public exceptions is to render a static html + # file with the name of the error code thrown. If no such file exists, an + # empty response is sent with the correct status code. + # + # You can override what constitutes a local request by overriding the + # <tt>local_request?</tt> method in your own controller. Custom rescue + # behavior is achieved by overriding the <tt>rescue_action_in_public</tt> + # and <tt>rescue_action_locally</tt> methods. + module Rescue + extend ActiveSupport::Concern + + included do + include ActiveSupport::Rescuable + end + + module ClassMethods + # This can be removed once we can move action(:_rescue_action) into middlewares.rb + # Currently, it does controller.method(:rescue_action), which is hiding the implementation + # difference between the old and new base. + def rescue_action(env) + action(:_rescue_action).call(env) + end + end + + attr_internal :rescued_exception + + private + def method_for_action(action_name) + return action_name if self.rescued_exception = request.env.delete("action_dispatch.rescue.exception") + super + end + + def _rescue_action + rescue_with_handler(rescued_exception) || raise(rescued_exception) + end + + def process_action(*) + super + rescue Exception => exception + self.rescued_exception = exception + _rescue_action + end + end +end diff --git a/actionpack/lib/action_controller/new_base/session.rb b/actionpack/lib/action_controller/new_base/session.rb new file mode 100644 index 0000000000..a585630230 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/session.rb @@ -0,0 +1,15 @@ +module ActionController + module Session + extend ActiveSupport::Concern + + depends_on RackConvenience + + def session + @_request.session + end + + def reset_session + @_request.reset_session + end + end +end diff --git a/actionpack/lib/action_controller/new_base/testing.rb b/actionpack/lib/action_controller/new_base/testing.rb new file mode 100644 index 0000000000..e8d210d149 --- /dev/null +++ b/actionpack/lib/action_controller/new_base/testing.rb @@ -0,0 +1,39 @@ +module ActionController + module Testing + extend ActiveSupport::Concern + + depends_on RackConvenience + + # OMG MEGA HAX + def process_with_new_base_test(request, response) + @_request = request + @_response = response + @_response.request = request + ret = process(request.parameters[:action]) + @_response.body ||= self.response_body + @_response.prepare! + set_test_assigns + ret + end + + def set_test_assigns + @assigns = {} + (instance_variable_names - self.class.protected_instance_variables).each do |var| + name, value = var[1..-1], instance_variable_get(var) + @assigns[name] = value + end + end + + # TODO : Rewrite tests using controller.headers= to use Rack env + def headers=(new_headers) + @_response ||= ActionDispatch::Response.new + @_response.headers.replace(new_headers) + end + + module ClassMethods + def before_filters + _process_action_callbacks.find_all{|x| x.kind == :before}.map{|x| x.name} + end + end + end +end diff --git a/actionpack/lib/action_controller/new_base/url_for.rb b/actionpack/lib/action_controller/new_base/url_for.rb index af5b21012b..902cac4d15 100644 --- a/actionpack/lib/action_controller/new_base/url_for.rb +++ b/actionpack/lib/action_controller/new_base/url_for.rb @@ -1,5 +1,14 @@ module ActionController module UrlFor + extend ActiveSupport::Concern + + depends_on RackConvenience + + def process_action(*) + initialize_current_url + super + end + def initialize_current_url @url = UrlRewriter.new(request, params.clone) end @@ -16,7 +25,7 @@ module ActionController # by this method. def default_url_options(options = nil) end - + def rewrite_options(options) #:nodoc: if defaults = default_url_options(options) defaults.merge(options) @@ -24,7 +33,7 @@ module ActionController options end end - + def url_for(options = {}) options ||= {} case options @@ -37,4 +46,4 @@ module ActionController end end end -end
\ No newline at end of file +end |