/
helpers.rb
243 lines (215 loc) · 7.64 KB
/
helpers.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# frozen_string_literal: true
# :markup: markdown
require "active_support/dependencies"
require "active_support/core_ext/name_error"
module AbstractController
module Helpers
extend ActiveSupport::Concern
included do
class_attribute :_helper_methods, default: Array.new
# This is here so that it is always higher in the inheritance chain than the
# definition in lib/action_view/rendering.rb
redefine_singleton_method(:_helpers) do
if @_helpers ||= nil
@_helpers
else
superclass._helpers
end
end
self._helpers = define_helpers_module(self)
end
def _helpers
self.class._helpers
end
module Resolution # :nodoc:
def modules_for_helpers(modules_or_helper_prefixes)
modules_or_helper_prefixes.flatten.map! do |module_or_helper_prefix|
case module_or_helper_prefix
when Module
module_or_helper_prefix
when String, Symbol
helper_prefix = module_or_helper_prefix.to_s
helper_prefix = helper_prefix.camelize unless helper_prefix.start_with?(/[A-Z]/)
"#{helper_prefix}Helper".constantize
else
raise ArgumentError, "helper must be a String, Symbol, or Module"
end
end
end
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
names = Dir["#{_path}/**/*_helper.rb"].map { |file| file[_path.to_s.size + 1..-"_helper.rb".size - 1] }
names.sort!
end
helpers.uniq!
helpers
end
def helper_modules_from_paths(paths)
modules_for_helpers(all_helpers_from_path(paths))
end
end
extend Resolution
module ClassMethods
# When a class is inherited, wrap its helper module in a new module. This
# ensures that the parent class's module can be changed independently of the
# child class's.
def inherited(klass)
# Inherited from parent by default
klass._helpers = nil
klass.class_eval { default_helper_module! } unless klass.anonymous?
super
end
attr_writer :_helpers
include Resolution
##
# :method: modules_for_helpers
# :call-seq: modules_for_helpers(modules_or_helper_prefixes)
#
# Given an array of values like the ones accepted by `helper`, this method
# returns an array with the corresponding modules, in the same order.
#
# ActionController::Base.modules_for_helpers(["application", "chart", "rubygems"])
# # => [ApplicationHelper, ChartHelper, RubygemsHelper]
#
#--
# Implemented by Resolution#modules_for_helpers.
# :method: # all_helpers_from_path
# :call-seq: all_helpers_from_path(path)
#
# Returns a list of helper names in a given path.
#
# ActionController::Base.all_helpers_from_path 'app/helpers'
# # => ["application", "chart", "rubygems"]
#
#--
# Implemented by Resolution#all_helpers_from_path.
# Declare a controller method as a helper. For example, the following
# makes the `current_user` and `logged_in?` controller methods available
# to the view:
# class ApplicationController < ActionController::Base
# helper_method :current_user, :logged_in?
#
# private
# def current_user
# @current_user ||= User.find_by(id: session[:user])
# end
#
# def logged_in?
# current_user != nil
# end
# end
#
# In a view:
# <% if logged_in? -%>Welcome, <%= current_user.name %><% end -%>
#
# #### Parameters
# * `method[, method]` - A name or names of a method on the controller to be
# made available on the view.
def helper_method(*methods)
methods.flatten!
self._helper_methods += methods
location = caller_locations(1, 1).first
file, line = location.path, location.lineno
methods.each do |method|
# def current_user(*args, &block)
# controller.send(:'current_user', *args, &block)
# end
_helpers_for_modification.class_eval <<~ruby_eval.lines.map(&:strip).join(";"), file, line
def #{method}(...)
controller.send(:'#{method}', ...)
end
ruby_eval
end
end
# Includes the given modules in the template class.
#
# Modules can be specified in different ways. All of the following calls include
# `FooHelper`:
#
# # Module, recommended.
# helper FooHelper
#
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo"
# helper :Foo
# helper "foo"
# helper :foo
#
# The last two assume that `"foo".camelize` returns "Foo".
#
# When strings or symbols are passed, the method finds the actual module object
# using String#constantize. Therefore, if the module has not been yet loaded, it
# has to be autoloadable, which is normally the case.
#
# Namespaces are supported. The following calls include `Foo::BarHelper`:
#
# # Module, recommended.
# helper Foo::BarHelper
#
# # String/symbol without the "helper" suffix, camel or snake case.
# helper "Foo::Bar"
# helper :"Foo::Bar"
# helper "foo/bar"
# helper :"foo/bar"
#
# The last two assume that `"foo/bar".camelize` returns "Foo::Bar".
#
# The method accepts a block too. If present, the block is evaluated in the
# context of the controller helper module. This simple call makes the `wadus`
# method available in templates of the enclosing controller:
#
# helper do
# def wadus
# "wadus"
# end
# end
#
# Furthermore, all the above styles can be mixed together:
#
# helper FooHelper, "woo", "bar/baz" do
# def wadus
# "wadus"
# end
# end
#
def helper(*args, &block)
modules_for_helpers(args).each do |mod|
next if _helpers.include?(mod)
_helpers_for_modification.include(mod)
end
_helpers_for_modification.module_eval(&block) if block_given?
end
# Clears up all existing helpers in this class, only keeping the helper with the
# same name as this class.
def clear_helpers
inherited_helper_methods = _helper_methods
self._helpers = Module.new
self._helper_methods = Array.new
inherited_helper_methods.each { |meth| helper_method meth }
default_helper_module! unless anonymous?
end
def _helpers_for_modification
unless @_helpers
self._helpers = define_helpers_module(self, superclass._helpers)
end
_helpers
end
private
def define_helpers_module(klass, helpers = nil)
# In some tests inherited is called explicitly. In that case, just return the
# module from the first time it was defined
return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
mod = Module.new
klass.const_set(:HelperMethods, mod)
mod.include(helpers) if helpers
mod
end
def default_helper_module!
helper_prefix = name.delete_suffix("Controller")
helper(helper_prefix)
rescue NameError => e
raise unless e.missing_name?("#{helper_prefix}Helper")
end
end
end
end