class Tapioca::Compilers::Dsl::ActionControllerHelpers

`Tapioca::Compilers::Dsl::ActionControllerHelpers` decorates RBI files for all subclasses of [`ActionController::Base`](api.rubyonrails.org/classes/ActionController/Helpers.html).

For example, with the following `MyHelper` module:

~~~rb module MyHelper

 def greet(user)
   # ...
 end

def localized_time
   # ...
 end

end ~~~

and the following controller:

~~~rb class UserController < ActionController::Base

helper MyHelper
helper { def age(user) "99" end }
helper_method :current_user_name

def current_user_name
  # ...
end

end ~~~

this generator will produce an RBI file `user_controller.rbi` with the following content:

~~~rbi # user_controller.rbi # typed: strong class UserController

module HelperMethods
  include MyHelper

  sig { params(user: T.untyped).returns(T.untyped) }
  def age(user); end

  sig { returns(T.untyped) }
  def current_user_name; end
end

class HelperProxy < ::ActionView::Base
  include HelperMethods
end

sig { returns(HelperProxy) }
def helpers; end

end ~~~

Public Instance Methods

decorate(root, constant) click to toggle source
# File lib/tapioca/compilers/dsl/action_controller_helpers.rb, line 76
def decorate(root, constant)
  helpers_module = constant._helpers
  proxied_helper_methods = constant._helper_methods.map(&:to_s).map(&:to_sym)

  helper_proxy_name = "HelperProxy"
  helper_methods_name = "HelperMethods"

  # Define the helpers method
  root.create_path(constant) do |controller|
    controller.create_method("helpers", return_type: helper_proxy_name)

    # Create helper method module
    controller.create_module(helper_methods_name) do |helper_methods|
      # If the controller has no helper defined, then it just inherits
      # the Action Controlller base helper methods module, so we should
      # just add that as an include and stop doing more processing.
      if helpers_module.name == "ActionController::Base::HelperMethods"
        next helper_methods.create_include(T.must(qualified_name_of(helpers_module)))
      end

      # Find all the included helper modules and generate an include
      # for each of those helper modules
      gather_includes(helpers_module).each do |ancestor|
        helper_methods.create_include(ancestor)
      end

      # Generate a method definition in the helper module for each
      # helper method defined via the `helper_method` call in the controller.
      helpers_module.instance_methods(false).each do |method_name|
        method = if proxied_helper_methods.include?(method_name)
          helper_method_proxy_target(constant, method_name)
        else
          helpers_module.instance_method(method_name)
        end

        if method
          create_method_from_def(helper_methods, method)
        else
          create_unknown_proxy_method(helper_methods, method_name)
        end
      end
    end

    # Create helper proxy class
    controller.create_class(helper_proxy_name, superclass_name: "::ActionView::Base") do |proxy|
      proxy.create_include(helper_methods_name)
    end
  end
end
gather_constants() click to toggle source
# File lib/tapioca/compilers/dsl/action_controller_helpers.rb, line 127
def gather_constants
  descendants_of(::ActionController::Base).reject(&:abstract?).select(&:name)
end

Private Instance Methods

create_unknown_proxy_method(helper_methods, method_name) click to toggle source
# File lib/tapioca/compilers/dsl/action_controller_helpers.rb, line 147
def create_unknown_proxy_method(helper_methods, method_name)
  helper_methods.create_method(
    method_name.to_s,
    parameters: [
      create_rest_param("args", type: "T.untyped"),
      create_kw_rest_param("kwargs", type: "T.untyped"),
      create_block_param("blk", type: "T.untyped"),
    ],
    return_type: "T.untyped"
  )
end
gather_includes(mod) click to toggle source
# File lib/tapioca/compilers/dsl/action_controller_helpers.rb, line 160
def gather_includes(mod)
  mod.ancestors
    .reject { |ancestor| ancestor.is_a?(Class) || ancestor == mod || ancestor.name.nil? }
    .map { |ancestor| T.must(qualified_name_of(ancestor)) }
    .reverse
end
helper_method_proxy_target(constant, method_name) click to toggle source
# File lib/tapioca/compilers/dsl/action_controller_helpers.rb, line 139
def helper_method_proxy_target(constant, method_name)
  # Lookup the proxy target method only if it is defined as a public/protected or private method.
  if constant.method_defined?(method_name) || constant.private_method_defined?(method_name)
    constant.instance_method(method_name)
  end
end