如果你小心的话,你可以monkeypatch。我试图实现它,看看它是否有效。显然,您会受到很大的性能影响,因此我不建议在生产中使用它:P 可以通过记住测试位置而不是每次调用该方法时都向上爬目录树来加快速度。
这个想法是如果在类上定义了原始方法,则将其移开,然后替换一个方法来检查您是否从代码中调用。我正在使用包含目录的.git
目录作为您的项目目录;如果您vendor
的项目目录中直接有一个目录,则它是豁免的,项目目录之外的所有内容也是如此。如果您位于豁免的位置,只需将内容传递给保存的旧方法或继承链;如果不是,请尖叫犯规。
require 'pathname'
PROJECT_CODE = Pathname.new(__dir__).ascend.find { |loc| (loc / '.git').directory? }
VENDOR_CODE = PROJECT_CODE / 'vendor'
class ForbiddenMethodError < StandardError; end
def forbid_method(klass, meth, &block)
case
when klass.instance_methods(false).include?(meth)
old_meth = :"forbid_method_old_#{meth}"
klass.alias_method old_meth, meth
when klass.respond_to?(meth)
old_meth = nil
else
raise ArgumentError, "No such method: #{klass}##{meth}"
end
klass.define_method(meth) do |*args|
if !block || instance_exec(*args, &block)
caller_loc = Pathname.new(caller_locations.first.path).expand_path
caller_loc.ascend do |ancestor|
case ancestor
when PROJECT_CODE
raise ForbiddenMethodError, "#{klass}##{meth}", caller[2..]
when VENDOR_CODE
break
end
end
end
if old_meth
send(old_meth, *args)
else
super(*args)
end
end
end
有了这个,您可以在项目代码中创建Float#to_d
和(有条件地)String#to_d
失败:
require 'bigdecimal/util'
forbid_method(Float, :to_d)
forbid_method(String, :to_d) { !BigDecimal(self) rescue true }
如果您传递一个块,则该功能仅在该块条件为真时才被禁止。(该块将传递所有方法的参数,并将与禁止方法的接收者一起执行self
。)