root/trunk/actionpack/lib/action_controller/helpers.rb
| Revision 9234, 8.4 kB (checked in by josh, 6 months ago) |
|---|
| Line | |
|---|---|
| 1 | # FIXME: helper { ... } is broken on Ruby 1.9 |
| 2 | module ActionController #:nodoc: |
| 3 | module Helpers #:nodoc: |
| 4 | HELPERS_DIR = (defined?(RAILS_ROOT) ? "#{RAILS_ROOT}/app/helpers" : "app/helpers") |
| 5 | |
| 6 | def self.included(base) |
| 7 | # Initialize the base module to aggregate its helpers. |
| 8 | base.class_inheritable_accessor :master_helper_module |
| 9 | base.master_helper_module = Module.new |
| 10 | |
| 11 | # Extend base with class methods to declare helpers. |
| 12 | base.extend(ClassMethods) |
| 13 | |
| 14 | base.class_eval do |
| 15 | # Wrap inherited to create a new master helper module for subclasses. |
| 16 | class << self |
| 17 | alias_method_chain :inherited, :helper |
| 18 | end |
| 19 | end |
| 20 | end |
| 21 | |
| 22 | # The Rails framework provides a large number of helpers for working with +assets+, +dates+, +forms+, |
| 23 | # +numbers+ and +ActiveRecord+ objects, to name a few. These helpers are available to all templates |
| 24 | # by default. |
| 25 | # |
| 26 | # In addition to using the standard template helpers provided in the Rails framework, creating custom helpers to |
| 27 | # extract complicated logic or reusable functionality is strongly encouraged. By default, the controller will |
| 28 | # include a helper whose name matches that of the controller, e.g., <tt>MyController</tt> will automatically |
| 29 | # include <tt>MyHelper</tt>. |
| 30 | # |
| 31 | # Additional helpers can be specified using the +helper+ class method in <tt>ActionController::Base</tt> or any |
| 32 | # controller which inherits from it. |
| 33 | # |
| 34 | # ==== Examples |
| 35 | # The +to_s+ method from the +Time+ class can be wrapped in a helper method to display a custom message if |
| 36 | # the Time object is blank: |
| 37 | # |
| 38 | # module FormattedTimeHelper |
| 39 | # def format_time(time, format=:long, blank_message=" ") |
| 40 | # time.blank? ? blank_message : time.to_s(format) |
| 41 | # end |
| 42 | # end |
| 43 | # |
| 44 | # +FormattedTimeHelper+ can now be included in a controller, using the +helper+ class method: |
| 45 | # |
| 46 | # class EventsController < ActionController::Base |
| 47 | # helper FormattedTimeHelper |
| 48 | # def index |
| 49 | # @events = Event.find(:all) |
| 50 | # end |
| 51 | # end |
| 52 | # |
| 53 | # Then, in any view rendered by <tt>EventController</tt>, the <tt>format_time</tt> method can be called: |
| 54 | # |
| 55 | # <% @events.each do |event| -%> |
| 56 | # <p> |
| 57 | # <% format_time(event.time, :short, "N/A") %> | <%= event.name %> |
| 58 | # </p> |
| 59 | # <% end -%> |
| 60 | # |
| 61 | # Finally, assuming we have two event instances, one which has a time and one which does not, |
| 62 | # the output might look like this: |
| 63 | # |
| 64 | # 23 Aug 11:30 | Carolina Railhawks Soccer Match |
| 65 | # N/A | Carolina Railhaws Training Workshop |
| 66 | # |
| 67 | module ClassMethods |
| 68 | # Makes all the (instance) methods in the helper module available to templates rendered through this controller. |
| 69 | # See ActionView::Helpers (link:classes/ActionView/Helpers.html) for more about making your own helper modules |
| 70 | # available to the templates. |
| 71 | def add_template_helper(helper_module) #:nodoc: |
| 72 | master_helper_module.module_eval { include helper_module } |
| 73 | end |
| 74 | |
| 75 | # The +helper+ class method can take a series of helper module names, a block, or both. |
| 76 | # |
| 77 | # * <tt>*args</tt>: One or more +Modules+, +Strings+ or +Symbols+, or the special symbol <tt>:all</tt>. |
| 78 | # * <tt>&block</tt>: A block defining helper methods. |
| 79 | # |
| 80 | # ==== Examples |
| 81 | # When the argument is a +String+ or +Symbol+, the method will provide the "_helper" suffix, require the file |
| 82 | # and include the module in the template class. The second form illustrates how to include custom helpers |
| 83 | # when working with namespaced controllers, or other cases where the file containing the helper definition is not |
| 84 | # in one of Rails' standard load paths: |
| 85 | # helper :foo # => requires 'foo_helper' and includes FooHelper |
| 86 | # helper 'resources/foo' # => requires 'resources/foo_helper' and includes Resources::FooHelper |
| 87 | # |
| 88 | # When the argument is a +Module+, it will be included directly in the template class. |
| 89 | # helper FooHelper # => includes FooHelper |
| 90 | # |
| 91 | # When the argument is the symbol <tt>:all</tt>, the controller will include all helpers from |
| 92 | # <tt>app/helpers/**/*.rb</tt> under +RAILS_ROOT+. |
| 93 | # helper :all |
| 94 | # |
| 95 | # Additionally, the +helper+ class method can receive and evaluate a block, making the methods defined available |
| 96 | # to the template. |
| 97 | # # One line |
| 98 | # helper { def hello() "Hello, world!" end } |
| 99 | # # Multi-line |
| 100 | # helper do |
| 101 | # def foo(bar) |
| 102 | # "#{bar} is the very best" |
| 103 | # end |
| 104 | # end |
| 105 | # |
| 106 | # Finally, all the above styles can be mixed together, and the +helper+ method can be invoked with a mix of |
| 107 | # +symbols+, +strings+, +modules+ and blocks. |
| 108 | # helper(:three, BlindHelper) { def mice() 'mice' end } |
| 109 | # |
| 110 | def helper(*args, &block) |
| 111 | args.flatten.each do |arg| |
| 112 | case arg |
| 113 | when Module |
| 114 | add_template_helper(arg) |
| 115 | when :all |
| 116 | helper(all_application_helpers) |
| 117 | when String, Symbol |
| 118 | file_name = arg.to_s.underscore + '_helper' |
| 119 | class_name = file_name.camelize |
| 120 | |
| 121 | begin |
| 122 | require_dependency(file_name) |
| 123 | rescue LoadError => load_error |
| 124 | requiree = / -- (.*?)(\.rb)?$/.match(load_error.message).to_a[1] |
| 125 | if requiree == file_name |
| 126 | msg = "Missing helper file helpers/#{file_name}.rb" |
| 127 | raise LoadError.new(msg).copy_blame!(load_error) |
| 128 | else |
| 129 | raise |
| 130 | end |
| 131 | end |
| 132 | |
| 133 | add_template_helper(class_name.constantize) |
| 134 | else |
| 135 | raise ArgumentError, "helper expects String, Symbol, or Module argument (was: #{args.inspect})" |
| 136 | end |
| 137 | end |
| 138 | |
| 139 | # Evaluate block in template class if given. |
| 140 | master_helper_module.module_eval(&block) if block_given? |
| 141 | end |
| 142 | |
| 143 | # Declare a controller method as a helper. For example, the following |
| 144 | # makes the +current_user+ controller method available to the view: |
| 145 | # class ApplicationController < ActionController::Base |
| 146 | # helper_method :current_user |
| 147 | # def current_user |
| 148 | # @current_user ||= User.find(session[:user]) |
| 149 | # end |
| 150 | # end |
| 151 | def helper_method(*methods) |
| 152 | methods.flatten.each do |method| |
| 153 | master_helper_module.module_eval <<-end_eval |
| 154 | def #{method}(*args, &block) |
| 155 | controller.send(%(#{method}), *args, &block) |
| 156 | end |
| 157 | end_eval |
| 158 | end |
| 159 | end |
| 160 | |
| 161 | # Declares helper accessors for controller attributes. For example, the |
| 162 | # following adds new +name+ and <tt>name=</tt> instance methods to a |
| 163 | # controller and makes them available to the view: |
| 164 | # helper_attr :name |
| 165 | # attr_accessor :name |
| 166 | def helper_attr(*attrs) |
| 167 | attrs.flatten.each { |attr| helper_method(attr, "#{attr}=") } |
| 168 | end |
| 169 | |
| 170 | # Provides a proxy to access helpers methods from outside the view. |
| 171 | def helpers |
| 172 | unless @helper_proxy |
| 173 | @helper_proxy = ActionView::Base.new |
| 174 | @helper_proxy.extend master_helper_module |
| 175 | else |
| 176 | @helper_proxy |
| 177 | end |
| 178 | end |
| 179 | |
| 180 | private |
| 181 | def default_helper_module! |
| 182 | unless name.blank? |
| 183 | module_name = name.sub(/Controller$|$/, 'Helper') |
| 184 | module_path = module_name.split('::').map { |m| m.underscore }.join('/') |
| 185 | require_dependency module_path |
| 186 | helper module_name.constantize |
| 187 | end |
| 188 | rescue MissingSourceFile => e |
| 189 | raise unless e.is_missing? module_path |
| 190 | rescue NameError => e |
| 191 | raise unless e.missing_name? module_name |
| 192 | end |
| 193 | |
| 194 | def inherited_with_helper(child) |
| 195 | inherited_without_helper(child) |
| 196 | |
| 197 | begin |
| 198 | child.master_helper_module = Module.new |
| 199 | child.master_helper_module.send! :include, master_helper_module |
| 200 | child.send! :default_helper_module! |
| 201 | rescue MissingSourceFile => e |
| 202 | raise unless e.is_missing?("helpers/#{child.controller_path}_helper") |
| 203 | end |
| 204 | end |
| 205 | |
| 206 | # Extract helper names from files in app/helpers/**/*.rb |
| 207 | def all_application_helpers |
| 208 | extract = /^#{Regexp.quote(HELPERS_DIR)}\/?(.*)_helper.rb$/ |
| 209 | Dir["#{HELPERS_DIR}/**/*_helper.rb"].map { |file| file.sub extract, '\1' } |
| 210 | end |
| 211 | end |
| 212 | end |
| 213 | end |
Note: See TracBrowser for help on using the browser.