查找给定类的文件路径
我有一个给定类的Class对象的引用。 我需要找出该类的文件路径。 如果有任何帮助,该类inheritance自ActiveRecord。
这样做的简单方法是什么?
谢谢。
在其方法上使用source_location
:
YourClass.instance_methods(false).map { |m| YourClass.instance_method(m).source_location.first }.uniq
您可能会获得多个位置,因为方法可能在不同的位置定义。
没有办法这样做适用于所有类定义。 有些直接的方法适用于某些情况,并且有一些复杂的方法适用于其他情况。
在Ruby中,可以多次重新打开类和模块以获得其他定义(猴子修补)。 对于类或模块,没有内置的主要定义概念。 也没有内置的方法来列出有助于定义类的所有文件。 但是,有一种内置方法可以列出定义类中方法的文件。 要查找贡献其他组件的静态定义(常量,声明等),可以遵循已知约定(如果适用)或应用静态源代码分析。
1.检查方法位置
Ruby方法在一个位置只有一个定义,可以通过Method#source_location
确定。 可以通过Class#instance_methods
及其作用域( public_
, protected_
和private_
)变体列出(作为符号)类或模块的实例方法。 单例方法(也称为类方法)可以通过Class#singleton_methods
列出。 将false
作为这些方法的第一个参数传递会导致它们省略从祖先inheritance的方法。 从其中一个符号中,可以通过Class#instance_method
获取相应的Method
,然后使用Method#source_location
获取Method#source_location
的文件和行号。 这适用于静态(使用def
)或动态定义的方法(使用各种方法,例如Module#class_eval
结合Module#define_method
)。
例如,考虑定义模块M
和类C
这些文件:
/tmp/m.rb
module M def self.extended(klass) klass.class_eval do define_method(:ifoo) do 'ifoo' end define_singleton_method(:cfoo) do 'cfoo' end end end def has_ibar self.class_eval do define_method(:ibar) do 'ibar' end end end def has_cbar self.class_eval do define_singleton_method(:cbar) do 'cbar' end end end end
/tmp/c.rb
require_relative 'm' class C extend M has_ibar has_cbar def im 'm' end def self.cm 'cm' end end
/tmp/c_ext.rb
class C def iext 'iext' end def self.cext 'cext' end end
根据这些定义,可以检查类并查找其源文件,如下面的Pry会话所示。
2.4.0 (main):0 > require '/tmp/c'; require '/tmp/c_ext'; 2.4.0 (main):0 > instance_methods_syms = C.instance_methods(false) => [:im, :ifoo, :ibar, :iext] 2.4.0 (main):0 > class_methods_syms = C.singleton_methods(false) => [:cm, :cfoo, :cbar, :cext] 2.4.0 (main):0 > instance_methods_locs = instance_methods_syms.map { |method_sym| C.instance_method(method_sym).source_location } => [["/tmp/c.rb", 9], ["/tmp/m.rb", 4], ["/tmp/m.rb", 16], ["/tmp/c_ext.rb", 2]] 2.4.0 (main):0 > class_methods_locs = class_methods_syms.map { |method_sym| C.singleton_class.instance_method(method_sym).source_location } => [["/tmp/c.rb", 13], ["/tmp/m.rb", 8], ["/tmp/m.rb", 24], ["/tmp/c_ext.rb", 6]] 2.4.0 (main):0 > methods_locs = instance_methods_locs + class_methods_locs; 2.4.0 (main):0 > class_files = methods_locs.map(&:first).uniq => ["/tmp/c.rb", "/tmp/m.rb", "/tmp/c_ext.rb"]
Module#instance_methods
返回的值的顺序未在文档中指定,并且在Ruby版本之间有所不同。
1.1识别主文件
在通过Module#instance_methods
和Method#source_location
获得的多个候选文件中识别类的主文件不是一个简单的问题。 在一般情况下,这是不可能的。
在上面的示例中,/ /tmp/c.rb
直观地是主文件,因为它是第一个定义C
require
d文件。 也许这就是为什么在Ruby 2.3.3和2.4.0中, Module#instance_methods
首先列出了它的方法。 但是,如上所述,订单没有记录,并且在Ruby版本之间有所不同。 请注意,按执行顺序在C
中定义的第一个方法是#ifoo
。 顺便说一句,使用Ruby 1.9.3到2.2.6, instance_methods_syms
的第一项是:ifoo
,因此第一项class_files
是/tmp/m.rb
显然不是任何人都会直观地考虑C
的主文件。
此外,考虑如果我们从/tmp/c.rb
删除方法定义会发生什么,只留下声明式调用extend M
, has_ibar
和has_cbar
。 在这种情况下, /tmp/c.rb
中完全没有class_files
。 这不是一个不切实际的场景。 例如,在Active Record中,简单模型类的主要定义可能仅包含validation和其他声明,而将其他所有内容都保留在框架中。 通过检查类的方法位置永远不会找到此定义。
1.2。 撬命命令show-source
Pry的show-source
(又名$
)命令使用这种方法的变体,将自己的逻辑应用于方法和类定义文件的检查和排序。 如果你很好奇,请参阅Pry::WrappedModule
和Pry::Method
。 它在实践中运行得相当好,但由于它依赖于Method#source_location
,因此无法找到不定义方法的类定义。
2.遵循惯例
此方法仅适用于根据某些明确定义的约定定义被检查类的情况。 如果您知道要检查的类遵循这样的约定,那么您可以使用它来确定地查找其主要定义。
即使方法定位方法失败 – 即主定义不包含任何方法定义时,此方法也可以工作。 但是,它仅限于遵循明确定义的约定的类定义。
2.1。 惯例:Rails模型类
在一个简单的Rails应用程序中,应用程序的模型类在其app/models
目录中定义,文件路径可以从类名确定性地派生。 给定这样的模型类klass
,包含其主要定义的文件位于以下位置:
Rails.root.join('app', 'models', "#{klass.name.underscore}.rb").to_s
例如,模型类ProductWidget
将在APP_ROOT/app/models/product_widget.rb
,其中APP_ROOT
是应用程序的根目录路径。
要概括这一点,必须考虑简单Rails配置的扩展。 在定义模型定义的自定义路径的Rails应用程序中,必须考虑所有这些路径。 此外,由于可以在应用程序加载的任何Rails引擎中定义任意模型类,因此还必须考虑其自定义路径,查看所有已加载的引擎。 以下代码结合了这些注意事项。
candidates = Rails.application.config.paths['app/models'].map do |model_root| Rails.root.join(model_root, "#{klass.name.underscore}.rb").to_s end candidates += Rails::Engine::Railties.engines.flat_map do |engine| engine.paths['app/models'].map do |model_root| engine.root.join(model_root, "#{klass.name.underscore}.rb").to_s end end candidates.find { |path| File.exist?(path) }
此示例特别适用于Rails模型,但它可以很容易地适应控制器和其定义位置受Rails约定和配置约束的其他类。
2.2。 惯例:Generic Rails自动加载解析
某些类在Rails应用程序中自动加载,但无法确定地标识为属于其路径在Rails路径配置中注册的标准类别(模型,控制器等)之一。 仍然,可以确定地识别包含这种类的主要定义的文件。 解决方案是实现Rails使用的通用自动加载解析算法 。 这种实现的一个例子超出了这个答案的范围。
3.静态源代码分析
如果其他方法不适用或不充分,可能会尝试采用暴力方法:在所有加载的Ruby源文件中查找给定类的定义。 不幸的是,这既有限又复杂。
使用Kernel#require
加载的文件列在$LOADED_FEATURES
,因此可以在路径数组中搜索包含类定义的Ruby文件。 但是,使用Kernel#load
加载的文件不一定列在任何地方,因此无法搜索它们。 一个例外是当config.cache_classes
为false(开发模式中的默认值)时通过Rails自动加载机制加载的文件。 在这种情况下,有一种解决方法:在Rails自动加载路径中搜索。 有效的搜索将遵循Rails自动加载解析算法 ,但它也足以搜索所有自动加载路径,这可以通过Rails.application.send(:_all_autoload_paths)
。
即使对于可以列出的类定义文件,识别给定类的定义也并非易事。 对于在根命名空间中使用class
语句定义的class
,这很容易:找到与/^\s*class\s+#{klass}[\s$]/
匹配的行。 但是,对于定义嵌套在module
体中的类,或者使用Class::new
动态定义的Class::new
,这需要将每个文件解析为抽象语法树(AST)并在树中搜索此类定义。 对于使用任何其他类生成器定义的类,需要使AST搜索知道该生成器。 考虑到任何此类实现都需要从磁盘读取许多文件,如果意图执行多个类定义查找,则应该谨慎地缓存所有发现的类定义。 任何此类实现都超出了本答案的范围。
对于不遵循明确定义的约定的文件中的类定义,此方法是最彻底的。 但是,实现很复杂,需要读取和解析所有加载的源文件,并且仍然基本上受到限制。
如果您指的是rails模型并采用默认配置,它应该在app models文件夹中,您可以获得路径
File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"