diff options
Diffstat (limited to 'actionpack/lib/abstract_controller')
-rw-r--r-- | actionpack/lib/abstract_controller/base.rb | 18 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/collector.rb | 2 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/compatibility.rb | 18 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/helpers.rb | 8 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/layouts.rb | 134 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/localized_cache.rb | 49 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/logger.rb | 4 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/rendering.rb | 212 | ||||
-rw-r--r-- | actionpack/lib/abstract_controller/view_paths.rb | 73 |
9 files changed, 187 insertions, 331 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 3119ee498b..c12b584144 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -1,3 +1,5 @@ +require 'active_support/ordered_options' + module AbstractController class Error < StandardError; end class ActionNotFound < StandardError; end @@ -5,7 +7,6 @@ module AbstractController class Base attr_internal :response_body attr_internal :action_name - attr_internal :formats class << self attr_reader :abstract @@ -28,6 +29,14 @@ module AbstractController @descendants ||= [] end + def config + @config ||= ActiveSupport::InheritableOptions.new(superclass < Base ? superclass.config : {}) + end + + def configure + yield config + end + # A list of all internal methods for a controller. This finds the first # abstract superclass of a controller, and gets a list of all public # instance methods on that abstract class. Public instance methods of @@ -84,15 +93,14 @@ module AbstractController # ==== Returns # String def controller_path - @controller_path ||= name && name.sub(/Controller$/, '').underscore + @controller_path ||= name.sub(/Controller$/, '').underscore unless anonymous? end end abstract! - # Initialize controller with nil formats. - def initialize #:nodoc: - @_formats = nil + def config + @config ||= ActiveSupport::InheritableOptions.new(self.class.config) end # Calls the action going through the entire action dispatch stack. diff --git a/actionpack/lib/abstract_controller/collector.rb b/actionpack/lib/abstract_controller/collector.rb index d429333661..81fb514770 100644 --- a/actionpack/lib/abstract_controller/collector.rb +++ b/actionpack/lib/abstract_controller/collector.rb @@ -1,3 +1,5 @@ +require "action_dispatch/http/mime_type" + module AbstractController module Collector def self.generate_method_for_mime(mime) diff --git a/actionpack/lib/abstract_controller/compatibility.rb b/actionpack/lib/abstract_controller/compatibility.rb deleted file mode 100644 index 7fb93a0eb5..0000000000 --- a/actionpack/lib/abstract_controller/compatibility.rb +++ /dev/null @@ -1,18 +0,0 @@ -module AbstractController - module Compatibility - extend ActiveSupport::Concern - - def _find_layout(name, details) - details[:prefix] = nil if name =~ /\blayouts/ - super - end - - # Move this into a "don't run in production" module - def _default_layout(details, require_layout = false) - super - rescue ActionView::MissingTemplate - _find_layout(_layout({}), {}) - nil - end - end -end diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 578b884a4d..f875213afb 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -1,6 +1,4 @@ require 'active_support/dependencies' -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' module AbstractController module Helpers @@ -27,7 +25,7 @@ module AbstractController def inherited(klass) helpers = _helpers klass._helpers = Module.new { include helpers } - klass.class_eval { default_helper_module! unless name.blank? } + klass.class_eval { default_helper_module! unless anonymous? } super end @@ -99,7 +97,7 @@ module AbstractController def helper(*args, &block) self._helper_serial = AbstractController::Helpers.next_serial + 1 - _modules_for_helpers(args).each do |mod| + modules_for_helpers(args).each do |mod| add_template_helper(mod) end @@ -134,7 +132,7 @@ module AbstractController # ==== Returns # Array[Module]:: A normalized list of modules for the list of # helpers provided. - def _modules_for_helpers(args) + def modules_for_helpers(args) args.flatten.map! do |arg| case arg when String, Symbol diff --git a/actionpack/lib/abstract_controller/layouts.rb b/actionpack/lib/abstract_controller/layouts.rb index 0d214396aa..2f9616124a 100644 --- a/actionpack/lib/abstract_controller/layouts.rb +++ b/actionpack/lib/abstract_controller/layouts.rb @@ -1,6 +1,3 @@ -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' - module AbstractController # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in # repeated setups. The inclusion pattern has pages that look like this: @@ -173,27 +170,7 @@ module AbstractController module ClassMethods def inherited(klass) super - klass.class_eval do - _write_layout_method - @found_layouts = {} - end - end - - def clear_template_caches! - @found_layouts.clear if defined? @found_layouts - super - end - - def cache_layout(details) - layout = @found_layouts - key = Thread.current[:format_locale_key] - - # Cache nil - if layout.key?(key) - return layout[key] - else - layout[key] = yield - end + klass._write_layout_method end # This module is mixed in if layout conditions are provided. This means @@ -262,10 +239,10 @@ module AbstractController def _write_layout_method case defined?(@_layout) ? @_layout : nil when String - self.class_eval %{def _layout(details) #{@_layout.inspect} end} + self.class_eval %{def _layout; #{@_layout.inspect} end} when Symbol self.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1 - def _layout(details) + def _layout #{@_layout}.tap do |layout| unless layout.is_a?(String) || !layout raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \ @@ -276,21 +253,21 @@ module AbstractController ruby_eval when Proc define_method :_layout_from_proc, &@_layout - self.class_eval %{def _layout(details) _layout_from_proc(self) end} + self.class_eval %{def _layout; _layout_from_proc(self) end} when false - self.class_eval %{def _layout(details) end} + self.class_eval %{def _layout; end} when true raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil" when nil if name + _prefix = "layouts" unless _implied_layout_name =~ /\blayouts/ + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def _layout(details) - self.class.cache_layout(details) do - if template_exists?("#{_implied_layout_name}", details, :_prefix => "layouts") - "#{_implied_layout_name}" - else - super - end + def _layout + if template_exists?("#{_implied_layout_name}", #{_prefix.inspect}) + "#{_implied_layout_name}" + else + super end end RUBY @@ -300,42 +277,20 @@ module AbstractController end end - def render_to_body(options = {}) - # In the case of a partial with a layout, handle the layout - # here, and make sure the view does not try to handle it - layout = options.delete(:layout) if options.key?(:partial) - - response = super + def _normalize_options(options) + super - # This is a little bit messy. We need to explicitly handle partial - # layouts here since the core lookup logic is in the view, but - # we need to determine the layout based on the controller - # - # TODO: An easier way to handle this would probably be to override - # render_template - if layout - layout = _layout_for_option(layout, options[:_template].details) - response = layout.render(view_context, options[:locals] || {}) { response } + if _include_layout?(options) + layout = options.key?(:layout) ? options.delete(:layout) : :default + value = _layout_for_option(layout) + options[:layout] = (value =~ /\blayouts/ ? value : "layouts/#{value}") if value end - - response end private # This will be overwritten by _write_layout_method - def _layout(details) end - - # Determine the layout for a given name and details. - # - # ==== Parameters - # name<String>:: The name of the template - # details<Hash{Symbol => Object}>:: A list of details to restrict - # the lookup to. By default, layout lookup is limited to the - # formats specified for the current request. - def _layout_for_name(name, details) - name && _find_layout(name, details) - end + def _layout; end # Determine the layout for a given name and details, taking into account # the name type. @@ -345,11 +300,11 @@ module AbstractController # details<Hash{Symbol => Object}>:: A list of details to restrict # the lookup to. By default, layout lookup is limited to the # formats specified for the current request. - def _layout_for_option(name, details) + def _layout_for_option(name) case name - when String then _layout_for_name(name, details) - when true then _default_layout(details, true) - when :default then _default_layout(details, false) + when String then name + when true then _default_layout(true) + when :default then _default_layout(false) when false, nil then nil else raise ArgumentError, @@ -357,29 +312,6 @@ module AbstractController end end - def _determine_template(options) - super - - return unless (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) - layout = options.key?(:layout) ? options[:layout] : :default - options[:_layout] = _layout_for_option(layout, options[:_template].details) - end - - # Take in the name and details and find a Template. - # - # ==== Parameters - # name<String>:: The name of the template to retrieve - # details<Hash>:: A list of details to restrict the search by. This - # might include details like the format or locale of the template. - # - # ==== Returns - # Template:: A template object matching the name and details - def _find_layout(name, details) - # TODO: Make prefix actually part of details in ViewPath#find_by_parts - prefix = details.key?(:prefix) ? details.delete(:prefix) : "layouts" - find_template(name, details, :_prefix => prefix) - end - # Returns the default layout for this controller and a given set of details. # Optionally raises an exception if the layout could not be found. # @@ -392,18 +324,24 @@ module AbstractController # # ==== Returns # Template:: The template object for the default layout (or nil) - def _default_layout(details, require_layout = false) - if require_layout && _action_has_layout? && !_layout(details) - raise ArgumentError, - "There was no default layout for #{self.class} in #{view_paths.inspect}" - end - + def _default_layout(require_layout = false) begin - _layout_for_name(_layout(details), details) if _action_has_layout? + layout_name = _layout if _action_has_layout? rescue NameError => e raise NoMethodError, "You specified #{@_layout.inspect} as the layout, but no such method was found" end + + if require_layout && _action_has_layout? && !layout_name + raise ArgumentError, + "There was no default layout for #{self.class} in #{view_paths.inspect}" + end + + layout_name + end + + def _include_layout?(options) + (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout) end def _action_has_layout? diff --git a/actionpack/lib/abstract_controller/localized_cache.rb b/actionpack/lib/abstract_controller/localized_cache.rb deleted file mode 100644 index 5e3efa002c..0000000000 --- a/actionpack/lib/abstract_controller/localized_cache.rb +++ /dev/null @@ -1,49 +0,0 @@ -module AbstractController - class HashKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|sh,sk| sh[sk] = {} } } - - def self.get(klass, formats, locale) - @hash_keys[klass][formats][locale] ||= new(klass, formats, locale) - end - - attr_accessor :hash - def initialize(klass, formats, locale) - @formats, @locale = formats, locale - @hash = [formats, locale].hash - end - - alias_method :eql?, :equal? - - def inspect - "#<HashKey -- formats: #{@formats.inspect} locale: #{@locale.inspect}>" - end - end - - module LocalizedCache - extend ActiveSupport::Concern - - module ClassMethods - def clear_template_caches! - ActionView::Partials::PartialRenderer::TEMPLATES.clear - template_cache.clear - super - end - - def template_cache - @template_cache ||= Hash.new {|h,k| h[k] = {} } - end - end - - def render(*args) - Thread.current[:format_locale_key] = HashKey.get(self.class, formats, I18n.locale) - super - end - - private - - def with_template_cache(name) - self.class.template_cache[Thread.current[:format_locale_key]][name] ||= super - end - - end -end diff --git a/actionpack/lib/abstract_controller/logger.rb b/actionpack/lib/abstract_controller/logger.rb index a23a13e1d6..9318f5e369 100644 --- a/actionpack/lib/abstract_controller/logger.rb +++ b/actionpack/lib/abstract_controller/logger.rb @@ -1,5 +1,5 @@ -require 'active_support/core_ext/logger' -require 'active_support/benchmarkable' +require "active_support/core_ext/logger" +require "active_support/benchmarkable" module AbstractController module Logger diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 619a49571b..42f4939108 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -1,7 +1,4 @@ require "abstract_controller/base" -require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/module/delegation' -require 'active_support/core_ext/array/wrap' module AbstractController class DoubleRenderError < Error @@ -12,32 +9,47 @@ module AbstractController end end + # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request, + # it will trigger the lookup_context and consequently expire the cache. + # TODO Add some deprecation warnings to remove I18n.locale from controllers + class I18nProxy < ::I18n::Config #:nodoc: + attr_reader :i18n_config, :lookup_context + + def initialize(i18n_config, lookup_context) + @i18n_config, @lookup_context = i18n_config, lookup_context + end + + def locale + @i18n_config.locale + end + + def locale=(value) + @i18n_config.locale = value + @lookup_context.update_details(:locale => @i18n_config.locale) + end + end + module Rendering extend ActiveSupport::Concern + include AbstractController::ViewPaths - included do - class_attribute :_view_paths - delegate :_view_paths, :to => :'self.class' - self._view_paths = ActionView::PathSet.new + # Overwrite process to setup I18n proxy. + def process(*) #:nodoc: + old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context) + super + ensure + I18n.config = old_config end # An instance of a view class. The default view class is ActionView::Base # # The view class must have the following methods: - # View.for_controller[controller] Create a new ActionView instance for a - # controller - # View#render_partial[options] - # - responsible for setting options[:_template] - # - Returns String with the rendered partial - # options<Hash>:: see _render_partial in ActionView::Base - # View#render_template[template, layout, options, partial] - # - Returns String with the rendered template - # template<ActionView::Template>:: The template to render - # layout<ActionView::Template>:: The layout to render around the template - # options<Hash>:: See _render_template_with_layout in ActionView::Base - # partial<Boolean>:: Whether or not the template to render is a partial + # View.for_controller[controller] + # Create a new ActionView instance for a controller + # View#render_template[options] + # Returns String with the rendered template # - # Override this method in a to change the default behavior. + # Override this method in a module to change the default behavior. def view_context @_view_context ||= ActionView::Base.for_controller(self) end @@ -45,57 +57,29 @@ module AbstractController # Mostly abstracts the fact that calling render twice is a DoubleRenderError. # Delegates render_to_body and sticks the result in self.response_body. def render(*args, &block) - options = _normalize_options(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) self.response_body = render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. - # - # ==== Options - # _partial_object<Object>:: The object that is being rendered. If this - # exists, we are in the special case of rendering an object as a partial. - # # :api: plugin def render_to_body(options = {}) - # TODO: Refactor so we can just use the normal template logic for this - if options.key?(:partial) - _render_partial(options) - else - _determine_template(options) - _render_template(options) - end + _process_options(options) + _render_template(options) end # Raw rendering of a template to a string. Just convert the results of # render_to_body into a String. - # # :api: plugin - def render_to_string(*args) - options = _normalize_options(*args) + def render_to_string(options={}) + _normalize_options(options) AbstractController::Rendering.body_to_s(render_to_body(options)) end - # Renders the template from an object. - # - # ==== Options - # _template<ActionView::Template>:: The template to render - # _layout<ActionView::Template>:: The layout to wrap the template in (optional) + # Find and renders a template based on the options given. def _render_template(options) - view_context.render_template(options) - end - - # Renders the given partial. - # - # ==== Options - # partial<String|Object>:: The partial name or the object to be rendered - def _render_partial(options) - view_context.render_partial(options) - end - - # The list of view paths for this controller. See ActionView::ViewPathSet for - # more details about writing custom view paths. - def view_paths - _view_paths + view_context.render_template(options) { |template| _with_template_hook(template) } end # The prefix used in render "foo" shortcuts. @@ -117,122 +101,42 @@ module AbstractController private - # Normalize options, by converting render "foo" to render :template => "prefix/foo" - # and render "/foo" to render :file => "/foo". - def _normalize_options(action=nil, options={}) + # Normalize options by converting render "foo" to render :action => "foo" and + # render "foo/bar" to render :file => "foo/bar". + def _normalize_args(action=nil, options={}) case action + when NilClass when Hash options, action = action, nil when String, Symbol action = action.to_s - case action.index("/") - when NilClass - options[:_prefix] = _prefix - options[:_template_name] = action - when 0 - options[:file] = action - else - options[:template] = action - end + key = action.include?(?/) ? :file : :action + options[key] = action + else + options.merge!(:partial => action) end options end - # Take in a set of options and determine the template to render - # - # ==== Options - # _template<ActionView::Template>:: If this is provided, the search is over - # _template_name<#to_s>:: The name of the template to look up. Otherwise, - # use the current action name. - # _prefix<String>:: The prefix to look inside of. In a file system, this corresponds - # to a directory. - # _partial<TrueClass, FalseClass>:: Whether or not the file to look up is a partial - def _determine_template(options) - if options.key?(:text) - options[:_template] = ActionView::Template::Text.new(options[:text], format_for_text) - elsif options.key?(:inline) - handler = ActionView::Template.handler_class_for_extension(options[:type] || "erb") - template = ActionView::Template.new(options[:inline], "inline template", handler, {}) - options[:_template] = template - elsif options.key?(:template) - options[:_template_name] = options[:template] - elsif options.key?(:file) - options[:_template_name] = options[:file] + def _normalize_options(options) + if options[:partial] == true + options[:partial] = action_name end - name = (options[:_template_name] || options[:action] || action_name).to_s - options[:_prefix] ||= _prefix if (options.keys & [:partial, :file, :template]).empty? - - details = _normalize_details(options) - options[:_template] ||= with_template_cache(name) do - find_template(name, details, options) + if (options.keys & [:partial, :file, :template]).empty? + options[:prefix] ||= _prefix end - end - - def _normalize_details(options) - details = { :formats => formats } - details[:formats] = Array(options[:format]) if options[:format] - details[:locale] = Array(options[:locale]) if options[:locale] - details - end - def find_template(name, details, options) - view_paths.find(name, details, options[:_prefix], options[:_partial]) - end - - def template_exists?(name, details, options) - view_paths.exists?(name, details, options[:_prefix], options[:_partial]) - end - - def with_template_cache(name) - yield + options[:template] ||= (options[:action] || action_name).to_s + options end - def format_for_text - Mime[:text] + def _process_options(options) end - module ClassMethods - def clear_template_caches! - end - - # Append a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def append_view_path(path) - self.view_paths = view_paths.dup + Array.wrap(path) - end - - # Prepend a path to the list of view paths for this controller. - # - # ==== Parameters - # path<String, ViewPath>:: If a String is provided, it gets converted into - # the default view path. You may also provide a custom view path - # (see ActionView::ViewPathSet for more information) - def prepend_view_path(path) - clear_template_caches! - self.view_paths = Array.wrap(path) + view_paths.dup - end - - # A list of all of the default view paths for this controller. - def view_paths - _view_paths - end - - # Set the view paths. - # - # ==== Parameters - # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; - # otherwise, process the parameter into a ViewPathSet. - def view_paths=(paths) - clear_template_caches! - self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) - _view_paths.freeze - end + def _with_template_hook(template) + self.formats = template.formats end end end diff --git a/actionpack/lib/abstract_controller/view_paths.rb b/actionpack/lib/abstract_controller/view_paths.rb new file mode 100644 index 0000000000..c2a9f6336d --- /dev/null +++ b/actionpack/lib/abstract_controller/view_paths.rb @@ -0,0 +1,73 @@ +module AbstractController + module ViewPaths + extend ActiveSupport::Concern + + included do + class_attribute :_view_paths + self._view_paths = ActionView::PathSet.new + end + + delegate :template_exists?, :view_paths, :formats, :formats=, + :locale, :locale=, :to => :lookup_context + + # LookupContext is the object responsible to hold all information required to lookup + # templates, i.e. view paths and details. Check ActionView::LookupContext for more + # information. + def lookup_context + @lookup_context ||= ActionView::LookupContext.new(self.class._view_paths, details_for_lookup) + end + + def details_for_lookup + { } + end + + def append_view_path(path) + lookup_context.view_paths.push(*path) + end + + def prepend_view_path(path) + lookup_context.view_paths.unshift(*path) + end + + def template_exists?(*args) + lookup_context.exists?(*args) + end + + module ClassMethods + # Append a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def append_view_path(path) + self.view_paths = view_paths.dup + Array(path) + end + + # Prepend a path to the list of view paths for this controller. + # + # ==== Parameters + # path<String, ViewPath>:: If a String is provided, it gets converted into + # the default view path. You may also provide a custom view path + # (see ActionView::ViewPathSet for more information) + def prepend_view_path(path) + self.view_paths = Array(path) + view_paths.dup + end + + # A list of all of the default view paths for this controller. + def view_paths + _view_paths + end + + # Set the view paths. + # + # ==== Parameters + # paths<ViewPathSet, Object>:: If a ViewPathSet is provided, use that; + # otherwise, process the parameter into a ViewPathSet. + def view_paths=(paths) + self._view_paths = paths.is_a?(ActionView::PathSet) ? paths : ActionView::Base.process_view_paths(paths) + self._view_paths.freeze + end + end + end +end
\ No newline at end of file |