layouts.rb 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. require "active_support/core_ext/module/remove_method"
  2. module AbstractController
  3. # Layouts reverse the common pattern of including shared headers and footers in many templates to isolate changes in
  4. # repeated setups. The inclusion pattern has pages that look like this:
  5. #
  6. # <%= render "shared/header" %>
  7. # Hello World
  8. # <%= render "shared/footer" %>
  9. #
  10. # This approach is a decent way of keeping common structures isolated from the changing content, but it's verbose
  11. # and if you ever want to change the structure of these two includes, you'll have to change all the templates.
  12. #
  13. # With layouts, you can flip it around and have the common structure know where to insert changing content. This means
  14. # that the header and footer are only mentioned in one place, like this:
  15. #
  16. # // The header part of this layout
  17. # <%= yield %>
  18. # // The footer part of this layout
  19. #
  20. # And then you have content pages that look like this:
  21. #
  22. # hello world
  23. #
  24. # At rendering time, the content page is computed and then inserted in the layout, like this:
  25. #
  26. # // The header part of this layout
  27. # hello world
  28. # // The footer part of this layout
  29. #
  30. # == Accessing shared variables
  31. #
  32. # Layouts have access to variables specified in the content pages and vice versa. This allows you to have layouts with
  33. # references that won't materialize before rendering time:
  34. #
  35. # <h1><%= @page_title %></h1>
  36. # <%= yield %>
  37. #
  38. # ...and content pages that fulfill these references _at_ rendering time:
  39. #
  40. # <% @page_title = "Welcome" %>
  41. # Off-world colonies offers you a chance to start a new life
  42. #
  43. # The result after rendering is:
  44. #
  45. # <h1>Welcome</h1>
  46. # Off-world colonies offers you a chance to start a new life
  47. #
  48. # == Layout assignment
  49. #
  50. # You can either specify a layout declaratively (using the #layout class method) or give
  51. # it the same name as your controller, and place it in <tt>app/views/layouts</tt>.
  52. # If a subclass does not have a layout specified, it inherits its layout using normal Ruby inheritance.
  53. #
  54. # For instance, if you have PostsController and a template named <tt>app/views/layouts/posts.html.erb</tt>,
  55. # that template will be used for all actions in PostsController and controllers inheriting
  56. # from PostsController.
  57. #
  58. # If you use a module, for instance Weblog::PostsController, you will need a template named
  59. # <tt>app/views/layouts/weblog/posts.html.erb</tt>.
  60. #
  61. # Since all your controllers inherit from ApplicationController, they will use
  62. # <tt>app/views/layouts/application.html.erb</tt> if no other layout is specified
  63. # or provided.
  64. #
  65. # == Inheritance Examples
  66. #
  67. # class BankController < ActionController::Base
  68. # # bank.html.erb exists
  69. #
  70. # class ExchangeController < BankController
  71. # # exchange.html.erb exists
  72. #
  73. # class CurrencyController < BankController
  74. #
  75. # class InformationController < BankController
  76. # layout "information"
  77. #
  78. # class TellerController < InformationController
  79. # # teller.html.erb exists
  80. #
  81. # class EmployeeController < InformationController
  82. # # employee.html.erb exists
  83. # layout nil
  84. #
  85. # class VaultController < BankController
  86. # layout :access_level_layout
  87. #
  88. # class TillController < BankController
  89. # layout false
  90. #
  91. # In these examples, we have three implicit lookup scenrios:
  92. # * The BankController uses the "bank" layout.
  93. # * The ExchangeController uses the "exchange" layout.
  94. # * The CurrencyController inherits the layout from BankController.
  95. #
  96. # However, when a layout is explicitly set, the explicitly set layout wins:
  97. # * The InformationController uses the "information" layout, explicitly set.
  98. # * The TellerController also uses the "information" layout, because the parent explicitly set it.
  99. # * The EmployeeController uses the "employee" layout, because it set the layout to nil, resetting the parent configuration.
  100. # * The VaultController chooses a layout dynamically by calling the <tt>access_level_layout</tt> method.
  101. # * The TillController does not use a layout at all.
  102. #
  103. # == Types of layouts
  104. #
  105. # Layouts are basically just regular templates, but the name of this template needs not be specified statically. Sometimes
  106. # you want to alternate layouts depending on runtime information, such as whether someone is logged in or not. This can
  107. # be done either by specifying a method reference as a symbol or using an inline method (as a proc).
  108. #
  109. # The method reference is the preferred approach to variable layouts and is used like this:
  110. #
  111. # class WeblogController < ActionController::Base
  112. # layout :writers_and_readers
  113. #
  114. # def index
  115. # # fetching posts
  116. # end
  117. #
  118. # private
  119. # def writers_and_readers
  120. # logged_in? ? "writer_layout" : "reader_layout"
  121. # end
  122. #
  123. # Now when a new request for the index action is processed, the layout will vary depending on whether the person accessing
  124. # is logged in or not.
  125. #
  126. # If you want to use an inline method, such as a proc, do something like this:
  127. #
  128. # class WeblogController < ActionController::Base
  129. # layout proc{ |controller| controller.logged_in? ? "writer_layout" : "reader_layout" }
  130. # end
  131. #
  132. # Of course, the most common way of specifying a layout is still just as a plain template name:
  133. #
  134. # class WeblogController < ActionController::Base
  135. # layout "weblog_standard"
  136. # end
  137. #
  138. # If no directory is specified for the template name, the template will by default be looked for in <tt>app/views/layouts/</tt>.
  139. # Otherwise, it will be looked up relative to the template root.
  140. #
  141. # Setting the layout to nil forces it to be looked up in the filesystem and fallbacks to the parent behavior if none exists.
  142. # Setting it to nil is useful to re-enable template lookup overriding a previous configuration set in the parent:
  143. #
  144. # class ApplicationController < ActionController::Base
  145. # layout "application"
  146. # end
  147. #
  148. # class PostsController < ApplicationController
  149. # # Will use "application" layout
  150. # end
  151. #
  152. # class CommentsController < ApplicationController
  153. # # Will search for "comments" layout and fallback "application" layout
  154. # layout nil
  155. # end
  156. #
  157. # == Conditional layouts
  158. #
  159. # If you have a layout that by default is applied to all the actions of a controller, you still have the option of rendering
  160. # a given action or set of actions without a layout, or restricting a layout to only a single action or a set of actions. The
  161. # <tt>:only</tt> and <tt>:except</tt> options can be passed to the layout call. For example:
  162. #
  163. # class WeblogController < ActionController::Base
  164. # layout "weblog_standard", :except => :rss
  165. #
  166. # # ...
  167. #
  168. # end
  169. #
  170. # This will assign "weblog_standard" as the WeblogController's layout for all actions except for the +rss+ action, which will
  171. # be rendered directly, without wrapping a layout around the rendered view.
  172. #
  173. # Both the <tt>:only</tt> and <tt>:except</tt> condition can accept an arbitrary number of method references, so
  174. # #<tt>:except => [ :rss, :text_only ]</tt> is valid, as is <tt>:except => :rss</tt>.
  175. #
  176. # == Using a different layout in the action render call
  177. #
  178. # If most of your actions use the same layout, it makes perfect sense to define a controller-wide layout as described above.
  179. # Sometimes you'll have exceptions where one action wants to use a different layout than the rest of the controller.
  180. # You can do this by passing a <tt>:layout</tt> option to the <tt>render</tt> call. For example:
  181. #
  182. # class WeblogController < ActionController::Base
  183. # layout "weblog_standard"
  184. #
  185. # def help
  186. # render :action => "help", :layout => "help"
  187. # end
  188. # end
  189. #
  190. # This will override the controller-wide "weblog_standard" layout, and will render the help action with the "help" layout instead.
  191. module Layouts
  192. extend ActiveSupport::Concern
  193. include Rendering
  194. included do
  195. class_attribute :_layout_conditions
  196. remove_possible_method :_layout_conditions
  197. self._layout_conditions = {}
  198. _write_layout_method
  199. end
  200. delegate :_layout_conditions, :to => "self.class"
  201. module ClassMethods
  202. def inherited(klass)
  203. super
  204. klass._write_layout_method
  205. end
  206. # This module is mixed in if layout conditions are provided. This means
  207. # that if no layout conditions are used, this method is not used
  208. module LayoutConditions
  209. # Determines whether the current action has a layout by checking the
  210. # action name against the :only and :except conditions set on the
  211. # layout.
  212. #
  213. # ==== Returns
  214. # * <tt> Boolean</tt> - True if the action has a layout, false otherwise.
  215. def conditional_layout?
  216. return unless super
  217. conditions = _layout_conditions
  218. if only = conditions[:only]
  219. only.include?(action_name)
  220. elsif except = conditions[:except]
  221. !except.include?(action_name)
  222. else
  223. true
  224. end
  225. end
  226. end
  227. # Specify the layout to use for this class.
  228. #
  229. # If the specified layout is a:
  230. # String:: the String is the template name
  231. # Symbol:: call the method specified by the symbol, which will return the template name
  232. # false:: There is no layout
  233. # true:: raise an ArgumentError
  234. # nil:: Force default layout behavior with inheritance
  235. #
  236. # ==== Parameters
  237. # * <tt>layout</tt> - The layout to use.
  238. #
  239. # ==== Options (conditions)
  240. # * :only - A list of actions to apply this layout to.
  241. # * :except - Apply this layout to all actions but this one.
  242. def layout(layout, conditions = {})
  243. include LayoutConditions unless conditions.empty?
  244. conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
  245. self._layout_conditions = conditions
  246. @_layout = layout
  247. _write_layout_method
  248. end
  249. # If no layout is supplied, look for a template named the return
  250. # value of this method.
  251. #
  252. # ==== Returns
  253. # * <tt>String</tt> - A template name
  254. def _implied_layout_name
  255. controller_path
  256. end
  257. # Creates a _layout method to be called by _default_layout .
  258. #
  259. # If a layout is not explicitly mentioned then look for a layout with the controller's name.
  260. # if nothing is found then try same procedure to find super class's layout.
  261. def _write_layout_method
  262. remove_possible_method(:_layout)
  263. prefixes = _implied_layout_name =~ /\blayouts/ ? [] : ["layouts"]
  264. name_clause = if name
  265. <<-RUBY
  266. lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first || super
  267. RUBY
  268. end
  269. if defined?(@_layout)
  270. layout_definition = case @_layout
  271. when String
  272. @_layout.inspect
  273. when Symbol
  274. <<-RUBY
  275. #{@_layout}.tap do |layout|
  276. unless layout.is_a?(String) || !layout
  277. raise ArgumentError, "Your layout method :#{@_layout} returned \#{layout}. It " \
  278. "should have returned a String, false, or nil"
  279. end
  280. end
  281. RUBY
  282. when Proc
  283. define_method :_layout_from_proc, &@_layout
  284. "_layout_from_proc(self)"
  285. when false
  286. nil
  287. when true
  288. raise ArgumentError, "Layouts must be specified as a String, Symbol, false, or nil"
  289. when nil
  290. name_clause
  291. end
  292. else
  293. # Add a deprecation if the parent layout was explicitly set and the child
  294. # still does a dynamic lookup. In next Rails release, we should @_layout
  295. # to be inheritable so we can skip the child lookup if the parent explicitly
  296. # set the layout.
  297. parent = self.superclass.instance_variable_get(:@_layout)
  298. @_layout = nil
  299. inspect = parent.is_a?(Proc) ? parent.inspect : parent
  300. layout_definition = if parent.nil?
  301. name_clause
  302. elsif name
  303. <<-RUBY
  304. if template = lookup_context.find_all("#{_implied_layout_name}", #{prefixes.inspect}).first
  305. ActiveSupport::Deprecation.warn 'Layout found at "#{_implied_layout_name}" for #{name} but parent controller ' \
  306. 'set layout to #{inspect.inspect}. Please explicitly set your layout to "#{_implied_layout_name}" ' \
  307. 'or set it to nil to force a dynamic lookup.'
  308. template
  309. else
  310. super
  311. end
  312. RUBY
  313. end
  314. end
  315. self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
  316. def _layout
  317. if conditional_layout?
  318. #{layout_definition}
  319. else
  320. #{name_clause}
  321. end
  322. end
  323. private :_layout
  324. RUBY
  325. end
  326. end
  327. def _normalize_options(options)
  328. super
  329. if _include_layout?(options)
  330. layout = options.key?(:layout) ? options.delete(:layout) : :default
  331. options[:layout] = _layout_for_option(layout)
  332. end
  333. end
  334. attr_internal_writer :action_has_layout
  335. def initialize(*)
  336. @_action_has_layout = true
  337. super
  338. end
  339. def action_has_layout?
  340. @_action_has_layout
  341. end
  342. def conditional_layout?
  343. true
  344. end
  345. private
  346. # This will be overwritten by _write_layout_method
  347. def _layout; end
  348. # Determine the layout for a given name, taking into account the name type.
  349. #
  350. # ==== Parameters
  351. # * <tt>name</tt> - The name of the template
  352. def _layout_for_option(name)
  353. case name
  354. when String then _normalize_layout(name)
  355. when Proc then name
  356. when true then Proc.new { _default_layout(true) }
  357. when :default then Proc.new { _default_layout(false) }
  358. when false, nil then nil
  359. else
  360. raise ArgumentError,
  361. "String, true, or false, expected for `layout'; you passed #{name.inspect}"
  362. end
  363. end
  364. def _normalize_layout(value)
  365. value.is_a?(String) && value !~ /\blayouts/ ? "layouts/#{value}" : value
  366. end
  367. # Returns the default layout for this controller.
  368. # Optionally raises an exception if the layout could not be found.
  369. #
  370. # ==== Parameters
  371. # * <tt>require_layout</tt> - If set to true and layout is not found,
  372. # an ArgumentError exception is raised (defaults to false)
  373. #
  374. # ==== Returns
  375. # * <tt>template</tt> - The template object for the default layout (or nil)
  376. def _default_layout(require_layout = false)
  377. begin
  378. value = _layout if action_has_layout?
  379. rescue NameError => e
  380. raise e, "Could not render layout: #{e.message}"
  381. end
  382. if require_layout && action_has_layout? && !value
  383. raise ArgumentError,
  384. "There was no default layout for #{self.class} in #{view_paths.inspect}"
  385. end
  386. _normalize_layout(value)
  387. end
  388. def _include_layout?(options)
  389. (options.keys & [:text, :inline, :partial]).empty? || options.key?(:layout)
  390. end
  391. end
  392. end