formtastic/formtastic

View on GitHub
lib/formtastic/namespaced_class_finder.rb

Summary

Maintainability
A
0 mins
Test Coverage
# 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