lib/formtastic/namespaced_class_finder.rb
# frozen_string_literal: true
module Formtastic
# This class implements class resolution in a namespace chain. It
# is used both by Formtastic::Helpers::InputHelper and
# Formtastic::Helpers::ActionHelper to look up action and input classes.
#
# @example Implementing own class finder
# # You can implement own class finder that for example prefixes the class name or uses custom module.
# class MyInputClassFinder < Formtastic::NamespacedClassFinder
# def initialize(namespaces)
# super [MyNamespace] + namespaces # first lookup in MyNamespace then the defaults
# end
#
# private
#
# def class_name(as)
# "My#{super}Input" # for example MyStringInput
# end
# end
#
# # in config/initializers/formtastic.rb
# Formtastic::FormBuilder.input_class_finder = MyInputClassFinder
#
class NamespacedClassFinder
attr_reader :namespaces # @private
# @private
class NotFoundError < NameError
end
def self.use_const_defined?
defined?(Rails) && ::Rails.application && ::Rails.application.config.respond_to?(:eager_load) && ::Rails.application.config.eager_load
end
def self.finder_method
@finder_method ||= use_const_defined? ? :find_with_const_defined : :find_by_trying
end
# @param namespaces [Array<Module>]
def initialize(namespaces)
@namespaces = namespaces.flatten
@cache = {}
end
# Looks up the given reference in the configured namespaces.
#
# Two finder methods are provided, one for development tries to
# reference the constant directly, triggering Rails' autoloading
# const_missing machinery; the second one instead for production
# checks with .const_defined before referencing the constant.
#
def find(as)
@cache[as] ||= resolve(as)
end
def resolve(as)
class_name = class_name(as)
finder(class_name) or raise NotFoundError, "class #{class_name}"
end
# Converts symbol to class name
# Overridden in subclasses to create `StringInput` and `ButtonAction`
# @example
# class_name(:string) == "String"
def class_name(as)
as.to_s.camelize
end
private
def finder(class_name) # @private
send(self.class.finder_method, class_name)
end
# Looks up the given class name in the configured namespaces in order,
# returning the first one that has the class name constant defined.
def find_with_const_defined(class_name)
@namespaces.find do |namespace|
if namespace.const_defined?(class_name)
break namespace.const_get(class_name)
end
end
end
# Use auto-loading in development environment
def find_by_trying(class_name)
@namespaces.find do |namespace|
begin
break namespace.const_get(class_name)
rescue NameError
end
end
end
end
end