查找给定类的文件路径

Fin*_*arr 14 ruby ruby-on-rails

我有一个给定类的Class对象的引用.我需要找出该类的文件路径.如果有任何帮助,该类继承自ActiveRecord.

这样做有简单的方法吗?

谢谢.

Mar*_*une 34

使用source_location其方法:

YourClass.instance_methods(false).map { |m| 
  YourClass.instance_method(m).source_location.first
}.uniq
Run Code Online (Sandbox Code Playgroud)

您可能会获得多个位置,因为方法可能在不同的位置定义.


nis*_*vid 6

没有办法做到这一点适用于所有类定义。有一些简单的方法适用于某些情况,也有一些复杂的方法适用于其他情况。

在 Ruby 中,可以多次重新打开类和模块以获取其他定义(猴子补丁)。没有内置的类或模块的主要定义概念。也没有内置的方法来列出所有有助于定义类的文件。但是,有一种内置方法可以列出在类中定义方法的文件。要找到有助于其他组件(常量、声明等)的静态定义,可以遵循已知约定(如果适用)或应用静态源代码分析。

1. 检查方法位置

一个 Ruby 方法在一个位置只有一个定义,可以通过Method#source_location. 类或模块的实例方法可以通过Class#instance_methods及其作用域(public_protected_、 和private_)变体列出(作为符号)。单例方法(又名类方法)可以通过Class#singleton_methods. false作为第一个参数传递给这些方法会导致它们省略从祖先继承的方法。从这些符号中的一个中,可以得到对应的Methodvia 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
Run Code Online (Sandbox Code Playgroud)

/tmp/c.rb

require_relative 'm'

class C
  extend M

  has_ibar
  has_cbar

  def im
    'm'
  end

  def self.cm
    'cm'
  end
end
Run Code Online (Sandbox Code Playgroud)

/tmp/c_ext.rb

class C
  def iext
    'iext'
  end

  def self.cext
    'cext'
  end
end
Run Code Online (Sandbox Code Playgroud)

鉴于这些定义,您可以检查该类并找到其源文件,如下面的 Pry 会话所示。

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
Run Code Online (Sandbox Code Playgroud)

返回值的顺序Module#instance_methods未在文档中指定,并且在 Ruby 版本之间有所不同。

1.1 识别主文件

在通过Module#instance_methods和获得的多个候选文件中识别一个类的主文件Method#source_location不是一个简单的问题。在一般情况下,这是不可能的。

在上面的例子中,/tmp/c.rb直观上是主文件,因为它是第一个require定义C. 也许这就是为什么在 Ruby 2.3.3 和 2.4.0 中,Module#instance_methods首先列出其方法的原因。但是,如上所述,该顺序没有记录,并且在 Ruby 版本之间有所不同。请注意,在 中定义的第一个方法按C执行顺序是#ifoo。顺便说一下,在 Ruby 1.9.3 到 2.2.6 中, 的第一项instance_methods_syms:ifooclass_files因此第一项是/tmp/m.rb——显然不是任何人直觉地认为C.

此外,考虑会发生什么,如果我们从去除方法的定义/tmp/c.rb,只留下了声明式的电话extend Mhas_ibarhas_cbar。在这种情况下,/tmp/c.rb从完全不存在class_files。这并不是一个不切实际的场景。例如,在 Active Record 中,一个简单模型类的主要定义可能只包含验证和其他声明,其他一切都由框架决定。通过检查类的方法位置永远不会找到这个定义。

1.2. 撬命令show-source

Pry show-source(又名$)命令使用这种方法的变体,将其自己的逻辑应用于方法和类定义文件的检查和排序。看看Pry::WrappedModulePry::Method如果你很好奇。它在实践中效果很好,但由于它依赖于Method#source_location,因此无法找到未定义方法的类定义。

2. 遵循惯例

这种方法仅适用于根据一些明确定义的约定定义被检查类的场景。如果您知道您正在检查的类遵循这样的约定,那么您可以使用它来确定地找到其主要定义。

即使在方法定位方法失败时(即当主要定义不包含任何方法定义时),这种方法也能工作。但是,它仅限于遵循明确定义的约定的类定义。

2.1. 约定:Rails 模型类

在一个简单的 Rails 应用程序中,应用程序的模型类在其app/models目录中定义,文件路径可以从类名确定地派生。给定这样的模型类klass,包含其主要定义的文件位于以下位置:

require_relative 'm'

class C
  extend M

  has_ibar
  has_cbar

  def im
    'm'
  end

  def self.cm
    'cm'
  end
end
Run Code Online (Sandbox Code Playgroud)

例如,模型类ProductWidget将在 中定义APP_ROOT/app/models/product_widget.rb,其中APP_ROOT是应用程序的根目录路径。

为了概括这一点,必须考虑对简单 Rails 配置的扩展。在为模型定义定义自定义路径的 Rails 应用程序中,必须考虑所有这些。此外,由于可以在应用程序加载的任何 Rails 引擎中定义任意模型类,因此还必须查看所有加载的引擎,并考虑它们的自定义路径。以下代码结合了这些注意事项。

class C
  def iext
    'iext'
  end

  def self.cext
    'cext'
  end
end
Run Code Online (Sandbox Code Playgroud)

这个例子特别适用于 Rails 模型,但它可以很容易地适应控制器和其他定义位置受 Rails 约定和配置约束的类。

2.2. 约定:通用 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根命名空间中的语句定义的类,这很容易:找到匹配/^\s*class\s+#{klass}[\s$]/. 但是,对于定义嵌套在module主体中的类,或使用动态定义的类Class::new,这需要将每个文件解析为抽象语法树 (AST) 并在树中搜索此类定义。对于使用任何其他类生成器定义的类,AST 搜索需要知道该生成器。考虑到任何此类实现都需要从磁盘读取许多文件,如果意图执行多个类定义查找,则缓存所有发现的类定义将是明智的。任何此类实现都超出了本答案的范围。

对于不遵循良好定义约定的文件中的类定义,这种方法是最彻底的方法。但是实现起来比较复杂,需要读取和解析所有加载的源文件,从根本上还是有局限性的。