Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/actionpack/lib/action_controller/helpers.rb

Revision 9234, 8.4 kB (checked in by josh, 6 months ago)

Provide a helper proxy to access helper methods from outside views. Closes #10839 [Josh Peek]

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="&nbsp;")
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.