rendering.rb 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. require "abstract_controller/base"
  2. require "action_view"
  3. require "active_support/core_ext/object/instance_variables"
  4. module AbstractController
  5. class DoubleRenderError < Error
  6. DEFAULT_MESSAGE = "Render and/or redirect were called multiple times in this action. Please note that you may only call render OR redirect, and at most once per action. Also note that neither redirect nor render terminate execution of the action, so if you want to exit an action after redirecting, you need to do something like \"redirect_to(...) and return\"."
  7. def initialize(message = nil)
  8. super(message || DEFAULT_MESSAGE)
  9. end
  10. end
  11. # This is a class to fix I18n global state. Whenever you provide I18n.locale during a request,
  12. # it will trigger the lookup_context and consequently expire the cache.
  13. class I18nProxy < ::I18n::Config #:nodoc:
  14. attr_reader :original_config, :lookup_context
  15. def initialize(original_config, lookup_context)
  16. original_config = original_config.original_config if original_config.respond_to?(:original_config)
  17. @original_config, @lookup_context = original_config, lookup_context
  18. end
  19. def locale
  20. @original_config.locale
  21. end
  22. def locale=(value)
  23. @lookup_context.locale = value
  24. end
  25. end
  26. module Rendering
  27. extend ActiveSupport::Concern
  28. include AbstractController::ViewPaths
  29. included do
  30. class_attribute :protected_instance_variables
  31. self.protected_instance_variables = []
  32. end
  33. # Overwrite process to setup I18n proxy.
  34. def process(*) #:nodoc:
  35. old_config, I18n.config = I18n.config, I18nProxy.new(I18n.config, lookup_context)
  36. super
  37. ensure
  38. I18n.config = old_config
  39. end
  40. module ClassMethods
  41. def view_context_class
  42. @view_context_class ||= begin
  43. routes = _routes if respond_to?(:_routes)
  44. helpers = _helpers if respond_to?(:_helpers)
  45. ActionView::Base.prepare(routes, helpers)
  46. end
  47. end
  48. end
  49. attr_internal_writer :view_context_class
  50. def view_context_class
  51. @_view_context_class ||= self.class.view_context_class
  52. end
  53. # An instance of a view class. The default view class is ActionView::Base
  54. #
  55. # The view class must have the following methods:
  56. # View.new[lookup_context, assigns, controller]
  57. # Create a new ActionView instance for a controller
  58. # View#render[options]
  59. # Returns String with the rendered template
  60. #
  61. # Override this method in a module to change the default behavior.
  62. def view_context
  63. view_context_class.new(view_renderer, view_assigns, self)
  64. end
  65. # Returns an object that is able to render templates.
  66. def view_renderer
  67. @_view_renderer ||= ActionView::Renderer.new(lookup_context)
  68. end
  69. # Normalize arguments, options and then delegates render_to_body and
  70. # sticks the result in self.response_body.
  71. def render(*args, &block)
  72. options = _normalize_render(*args, &block)
  73. self.response_body = render_to_body(options)
  74. end
  75. # Raw rendering of a template to a string. Just convert the results of
  76. # render_response into a String.
  77. # :api: plugin
  78. def render_to_string(*args, &block)
  79. options = _normalize_render(*args, &block)
  80. render_to_body(options)
  81. end
  82. # Raw rendering of a template to a Rack-compatible body.
  83. # :api: plugin
  84. def render_to_body(options = {})
  85. _process_options(options)
  86. _render_template(options)
  87. end
  88. # Find and renders a template based on the options given.
  89. # :api: private
  90. def _render_template(options) #:nodoc:
  91. lookup_context.rendered_format = nil if options[:formats]
  92. view_renderer.render(view_context, options)
  93. end
  94. DEFAULT_PROTECTED_INSTANCE_VARIABLES = %w(
  95. @_action_name @_response_body @_formats @_prefixes @_config
  96. @_view_context_class @_view_renderer @_lookup_context
  97. )
  98. # This method should return a hash with assigns.
  99. # You can overwrite this configuration per controller.
  100. # :api: public
  101. def view_assigns
  102. hash = {}
  103. variables = instance_variable_names
  104. variables -= protected_instance_variables
  105. variables -= DEFAULT_PROTECTED_INSTANCE_VARIABLES
  106. variables.each { |name| hash[name.to_s[1, name.length]] = instance_variable_get(name) }
  107. hash
  108. end
  109. private
  110. # Normalize args and options.
  111. # :api: private
  112. def _normalize_render(*args, &block)
  113. options = _normalize_args(*args, &block)
  114. _normalize_options(options)
  115. options
  116. end
  117. # Normalize args by converting render "foo" to render :action => "foo" and
  118. # render "foo/bar" to render :file => "foo/bar".
  119. # :api: plugin
  120. def _normalize_args(action=nil, options={})
  121. case action
  122. when NilClass
  123. when Hash
  124. options = action
  125. when String, Symbol
  126. action = action.to_s
  127. key = action.include?(?/) ? :file : :action
  128. options[key] = action
  129. else
  130. options[:partial] = action
  131. end
  132. options
  133. end
  134. # Normalize options.
  135. # :api: plugin
  136. def _normalize_options(options)
  137. if options[:partial] == true
  138. options[:partial] = action_name
  139. end
  140. if (options.keys & [:partial, :file, :template]).empty?
  141. options[:prefixes] ||= _prefixes
  142. end
  143. options[:template] ||= (options[:action] || action_name).to_s
  144. options
  145. end
  146. # Process extra options.
  147. # :api: plugin
  148. def _process_options(options)
  149. end
  150. end
  151. end