helpers.rb 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. require 'active_support/dependencies'
  2. module AbstractController
  3. module Helpers
  4. extend ActiveSupport::Concern
  5. included do
  6. class_attribute :_helpers
  7. self._helpers = Module.new
  8. class_attribute :_helper_methods
  9. self._helper_methods = Array.new
  10. end
  11. module ClassMethods
  12. # When a class is inherited, wrap its helper module in a new module.
  13. # This ensures that the parent class's module can be changed
  14. # independently of the child class's.
  15. def inherited(klass)
  16. helpers = _helpers
  17. klass._helpers = Module.new { include helpers }
  18. klass.class_eval { default_helper_module! unless anonymous? }
  19. super
  20. end
  21. # Declare a controller method as a helper. For example, the following
  22. # makes the +current_user+ controller method available to the view:
  23. # class ApplicationController < ActionController::Base
  24. # helper_method :current_user, :logged_in?
  25. #
  26. # def current_user
  27. # @current_user ||= User.find_by_id(session[:user])
  28. # end
  29. #
  30. # def logged_in?
  31. # current_user != nil
  32. # end
  33. # end
  34. #
  35. # In a view:
  36. # <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
  37. #
  38. # ==== Parameters
  39. # * <tt>method[, method]</tt> - A name or names of a method on the controller
  40. # to be made available on the view.
  41. def helper_method(*meths)
  42. meths.flatten!
  43. self._helper_methods += meths
  44. meths.each do |meth|
  45. _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
  46. def #{meth}(*args, &blk)
  47. controller.send(%(#{meth}), *args, &blk)
  48. end
  49. ruby_eval
  50. end
  51. end
  52. # The +helper+ class method can take a series of helper module names, a block, or both.
  53. #
  54. # ==== Parameters
  55. # * <tt>*args</tt> - Module, Symbol, String, :all
  56. # * <tt>block</tt> - A block defining helper methods
  57. #
  58. # ==== Examples
  59. # When the argument is a module it will be included directly in the template class.
  60. # helper FooHelper # => includes FooHelper
  61. #
  62. # When the argument is a string or symbol, the method will provide the "_helper" suffix, require the file
  63. # and include the module in the template class. The second form illustrates how to include custom helpers
  64. # when working with namespaced controllers, or other cases where the file containing the helper definition is not
  65. # in one of Rails' standard load paths:
  66. # helper :foo # => requires 'foo_helper' and includes FooHelper
  67. # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper
  68. #
  69. # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available
  70. # to the template.
  71. #
  72. # # One line
  73. # helper { def hello() "Hello, world!" end }
  74. #
  75. # # Multi-line
  76. # helper do
  77. # def foo(bar)
  78. # "#{bar} is the very best"
  79. # end
  80. # end
  81. #
  82. # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of
  83. # +symbols+, +strings+, +modules+ and blocks.
  84. #
  85. # helper(:three, BlindHelper) { def mice() 'mice' end }
  86. #
  87. def helper(*args, &block)
  88. modules_for_helpers(args).each do |mod|
  89. add_template_helper(mod)
  90. end
  91. _helpers.module_eval(&block) if block_given?
  92. end
  93. # Clears up all existing helpers in this class, only keeping the helper
  94. # with the same name as this class.
  95. def clear_helpers
  96. inherited_helper_methods = _helper_methods
  97. self._helpers = Module.new
  98. self._helper_methods = Array.new
  99. inherited_helper_methods.each { |meth| helper_method meth }
  100. default_helper_module! unless anonymous?
  101. end
  102. # Returns a list of modules, normalized from the acceptable kinds of
  103. # helpers with the following behavior:
  104. #
  105. # String or Symbol:: :FooBar or "FooBar" becomes "foo_bar_helper",
  106. # and "foo_bar_helper.rb" is loaded using require_dependency.
  107. #
  108. # Module:: No further processing
  109. #
  110. # After loading the appropriate files, the corresponding modules
  111. # are returned.
  112. #
  113. # ==== Parameters
  114. # * <tt>args</tt> - An array of helpers
  115. #
  116. # ==== Returns
  117. # * <tt>Array</tt> - A normalized list of modules for the list of
  118. # helpers provided.
  119. def modules_for_helpers(args)
  120. args.flatten.map! do |arg|
  121. case arg
  122. when String, Symbol
  123. file_name = "#{arg.to_s.underscore}_helper"
  124. require_dependency(file_name, "Missing helper file helpers/%s.rb")
  125. file_name.camelize.constantize
  126. when Module
  127. arg
  128. else
  129. raise ArgumentError, "helper must be a String, Symbol, or Module"
  130. end
  131. end
  132. end
  133. private
  134. # Makes all the (instance) methods in the helper module available to templates
  135. # rendered through this controller.
  136. #
  137. # ==== Parameters
  138. # * <tt>module</tt> - The module to include into the current helper module
  139. # for the class
  140. def add_template_helper(mod)
  141. _helpers.module_eval { include mod }
  142. end
  143. def default_helper_module!
  144. module_name = name.sub(/Controller$/, '')
  145. module_path = module_name.underscore
  146. helper module_path
  147. rescue MissingSourceFile => e
  148. raise e unless e.is_missing? "helpers/#{module_path}_helper"
  149. rescue NameError => e
  150. raise e unless e.missing_name? "#{module_name}Helper"
  151. end
  152. end
  153. end
  154. end