Skip to content

Instantly share code, notes, and snippets.

@estum
Created March 31, 2022 08:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save estum/b6ffbd6c3595229ab38c1051118deda5 to your computer and use it in GitHub Desktop.
Save estum/b6ffbd6c3595229ab38c1051118deda5 to your computer and use it in GitHub Desktop.
Module::ExpandConstant
# frozen_string_literal: true
require 'concurrent/map'
# = Expand Constant Namespace
#
# To expand namespace in classic way it have to be included or extended
# to a target module or class.
#
# This module provides +expand_constant+ method to expand external module namespace
# only inside a block also (with support of nesting) without copying all constants
# to a target. It is implemented by <tt>Module#const_missing</tt>.
#
# @example
#
# require 'module/expand_constant'
#
# Module.prepend Module::ExpandConstant
#
# module Foo
# expand_constant 'Arel::Nodes' do
# p Node # => Arel::Nodes::Node
#
# expand_constant 'ActiveRecord' do
# p Node # => Arel::Nodes::Node
# p Relation # => ActiveRecord::Relation
# end
#
# p Node # => Arel::Nodes::Node
# p Relation # => raise NameError: Foo::Relation
# end
#
# p Node # => raise NameError: Foo::Node
# end
#
module Module::ExpandConstant
# @return [Concurrent::Map]
def self.cache
@cache ||= Concurrent::Map.new
end
# @return [Boolean]
def self.allowed?(klass, name)
!!@cache.get([klass.object_id, name].hash)
end
TEMPLATE = <<~RUBY
def const_missing(name)
return ::%1$s.const_get(name) if ::Module::ExpandConstant.allowed?(self, %1$p) && ::%1$s.const_defined?(name)
return super if defined?(super)
const_get(name)
end
RUBY
# @!visibility public
# @param mod [String] namespace module name
# @return [void]
def expand_constant(mod)
mixin = Module.new { class_eval(TEMPLATE % mod) }
input = [object_id, mod].hash
begin
::Module::ExpandConstant.cache.put(input, true)
singleton_class.prepend(mixin)
yield
ensure
::Module::ExpandConstant.cache.put(input, false)
end
end
private_constant :TEMPLATE
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment