1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/actionpack/lib/abstract_controller/helpers.rb
Xavier Noria 5b28a0e972 Remove require_dependency usage in helper [Closes #37632]
Motivation is twofold:

  * We are gradually removing `require_dependency` from the framework.

  * Let `helper` work if `config.add_autoload_paths_to_load_path` is
    disabled.

Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2020-05-02 17:57:55 +02:00

184 lines
6.2 KiB
Ruby

# frozen_string_literal: true
require "active_support/dependencies"
module AbstractController
module Helpers
extend ActiveSupport::Concern
included do
class_attribute :_helpers, default: define_helpers_module(self)
class_attribute :_helper_methods, default: Array.new
end
class MissingHelperError < LoadError
def initialize(error, path)
@error = error
@path = "helpers/#{path}.rb"
set_backtrace error.backtrace
if /^#{path}(\.rb)?$/.match?(error.path)
super("Missing helper file helpers/%s.rb" % path)
else
raise error
end
end
end
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)
helpers = _helpers
klass._helpers = define_helpers_module(klass, helpers)
klass.class_eval { default_helper_module! } unless klass.anonymous?
super
end
# 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?
#
# 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
# * <tt>method[, method]</tt> - 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|
_helpers.class_eval <<-ruby_eval, file, line
def #{method}(*args, &block) # def current_user(*args, &block)
controller.send(:'#{method}', *args, &block) # controller.send(:'current_user', *args, &block)
end # end
ruby2_keywords(:'#{method}') if respond_to?(:ruby2_keywords, true)
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 <tt>"foo".camelize</tt> 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 <tt>"foo/bar".camelize</tt> 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|
_helpers.include(mod)
end
_helpers.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
# Given an array of values like the ones accepted by +helper+, this method
# returns an array with the corresponding modules, in the same order.
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
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