我有一个给定类的 Class 对象的引用。我需要找出类的文件路径。如果有任何帮助,该类继承自 ActiveRecord。
有什么简单的方法吗?
谢谢。
source_location
在其方法上使用:
YourClass.instance_methods(false).map { |m|
YourClass.instance_method(m).source_location.first
}.uniq
您可能会获得多个位置,因为方法可能定义在不同的位置。
没有办法适用于所有类定义。有一些直接的方法适用于某些情况,也有一些复杂的方法适用于其他情况。
在 Ruby 中,可以多次重新打开类和模块以进行附加定义(猴子补丁)。没有内置的类或模块的主要定义概念。也没有内置的方法来列出所有有助于类定义的文件。但是,有一种内置方法可以列出定义类中方法的文件。要找到有助于其他组件(常量、声明等)的静态定义,可以遵循已知约定(如果适用)或应用静态源代码分析。
Ruby 方法在一个位置只有一个定义,可以通过Method#source_location
. 类或模块的实例方法可以通过Class#instance_methods
及其作用域(public_
、、protected_
和private_
)变体列出(作为符号)。单例方法(又名类方法)可以通过Class#singleton_methods
. 作为第一个参数传递false
给这些方法会导致它们省略从祖先继承的方法。从这些符号之一可以得到对应的Method
via Class#instance_method
,然后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 版本之间有所不同。
在通过Module#instance_methods
和获得的多个候选文件中识别一个类的主文件Method#source_location
不是一个简单的问题。在一般情况下,这是不可能的。
在上面的例子中,/tmp/c.rb
直观上是主文件,因为它是第一个require
定义C
. 也许这就是为什么在 Ruby 2.3.3 和 2.4.0Module#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 中,简单模型类的主要定义可能只包含验证和其他声明,而其他一切都由框架决定。通过检查类的方法位置永远不会找到这个定义。
show-source
Pry 的show-source
(aka $
)命令使用这种方法的一种变体,将其自己的逻辑应用于方法和类定义文件的检查和排序。看看Pry::WrappedModule
,Pry::Method
如果你很好奇。它在实践中工作得相当好,但是因为它依赖于Method#source_location
,所以它无法找到没有定义方法的类定义。
此方法仅适用于根据某些明确定义的约定定义要检查的类的场景。如果您知道您正在检查的类遵循这样的约定,那么您可以使用它确定地找到它的主要定义。
即使方法定位方法失败,即当主定义不包含任何方法定义时,这种方法仍然有效。但是,它仅限于遵循明确约定的类定义。
在一个简单的 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 约定和配置约束的类。
某些类在 Rails 应用程序中自动加载,但无法确定地识别为属于其路径已在 Rails 路径配置中注册的标准类别之一(模型、控制器等)。尽管如此,仍然可以确定性地识别包含此类的主要定义的文件。解决方案是实现Rails 使用的通用自动加载解析算法。这种实现的一个例子超出了这个答案的范围。
如果其他方法不适用或不充分,可以尝试使用蛮力方法:在所有加载的 Ruby 源文件中查找给定类的定义。不幸的是,这既有限又复杂。
加载的文件Kernel#require
列在 中$LOADED_FEATURES
,因此可以在路径数组中搜索包含类定义的 Ruby 文件。但是,使用加载的文件Kernel#load
不一定会在任何地方列出,因此无法搜索它们。一个例外是在config.cache_classes
为 false 时通过 Rails 自动加载机制加载的文件(开发模式下的默认值)。在这种情况下,有一个解决方法:在 Rails 自动加载路径中搜索。有效的搜索将遵循Rails 自动加载解析算法,但搜索所有自动加载路径也足够了,可以通过Rails.application.send(:_all_autoload_paths)
.
即使对于可以列出的类定义文件,识别给定类的定义也并非易事。对于使用根命名空间中的语句定义的类class
,这很容易:找到匹配的行/^\s*class\s+#{klass}[\s$]/
。但是,对于定义嵌套在module
主体中的类,或者对于使用动态定义的类Class::new
,这需要将每个文件解析为抽象语法树 (AST) 并在树中搜索此类定义。对于使用任何其他类生成器定义的类,AST 搜索需要知道该生成器。考虑到任何这样的实现都需要从磁盘读取许多文件,如果意图执行多个类定义查找,缓存所有发现的类定义将是谨慎的。任何这样的实现都超出了这个答案的范围。
对于不遵循明确约定的文件中的类定义,这种方法是最彻底的方法。但是,实现很复杂,需要读取和解析所有加载的源文件,并且从根本上仍然受到限制。
如果您指的是 rails 模型并采用默认配置,它应该位于 app models 文件夹中,您可以获得如下路径
File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"