Это не красиво, но я смог заставить это работать, используя драгоценный камень binding_of_caller
.Просто сделайте gem install binding_of_caller
, и приведенный ниже код должен работать для того, что вам нужно.
Обратите внимание, что это сильно скопировано с https://stackoverflow.com/a/10535890/2230115, и вам определенно следует проголосовать за него:)
require 'binding_of_caller'
class Object
@@module_scoped_methods = []
def module_visibility(method_name, module_name)
method_name = method_name.to_sym
module_name = module_name.to_sym
new_method_name = "module_only_#{method_name}".to_sym
self.send(:alias_method, new_method_name, method_name)
self.send(:undef_method, method_name)
self.send(:private, new_method_name)
@@module_scoped_methods << [method_name, module_name]
end
alias_method :old_method_missing, :method_missing
undef_method :method_missing
def method_missing(method_name, *arg, &block)
method_ob = @@module_scoped_methods.find { |m| m[0] == method_name }
if method_ob
if binding.of_caller(1).eval('self.class').ancestors.find {|m| m.name.split('::').map{|c|c.to_sym}.include? method_ob[1]}
self.send("module_only_#{method_name}".to_sym,*arg,&block)
else
raise "Method #{method_name} is called outside the set module."
end
else
old_method_missing(method_name, arg, block)
end
end
end
Использование:
module A
class Test
def test
5
end
#Method name, and module wanted to be scoped to.
#Required since you can have nested modules.
module_visibility :test, :A
end
class B
def test
A::Test.new.test
end
end
end
# raises runtime error "method test is called outside the set module
A::Test.new.test
A::B.new.test # => 5
Как это работает:
Мы изменяем класс Object, чтобы отслеживать список методов, которые были ограничены видимостью модуля.Здесь хранится двухэлементный массив с именем метода и модулем, к которому он был применен.Затем он меняет имя метода на module_only_<originalname>
и делает его закрытым.
Затем, чтобы мы могли его вызвать, мы переопределяем method_missing
, чтобы он проверял этот метод в списке методов.мы присвоили области видимости модуля.Он проверяет предков класса вызывающего для модуля, к которому он был применен.Если какой-либо из предков является модулем, к которому он был применен, он вызывает метод.Если нет, возникает ошибка, которая не вызывается в модуле, к которому вы его добавили.