Подтвердить что ты не робот

Найти путь к файлу данного класса

У меня есть ссылка на объект класса данного класса. Мне нужно узнать путь к файлу класса. Класс наследуется от ActiveRecord, если это какая-то помощь.

Любой простой способ сделать это?

Спасибо.

4b9b3361

Ответ 1

Используйте source_location для своих методов:

YourClass.instance_methods(false).map { |m| 
  YourClass.instance_method(m).source_location.first
}.uniq

Вы можете получить более одного места, поскольку методы могут быть определены в разных местах.

Ответ 2

Нет никакого способа сделать это, который работает для всех определений классов. Есть несколько простых способов, которые работают для некоторых случаев, и есть несколько сложных способов, которые работают для других случаев.

В Ruby классы и модули могут быть повторно открыты для дополнительных определений (обезьяна исправлена) много раз. Нет встроенного понятия первичного определения для класса или модуля. Также нет встроенного способа перечислить все файлы, которые вносят вклад в определение класса. Тем не менее, есть встроенный способ перечислить файлы, которые определяют методы внутри класса. Чтобы найти статические определения, которые вносят вклад в другие компоненты (константы, декларации и т.д.), Можно следовать известным соглашениям (если применимо) или применять статический анализ исходного кода.

1. Осмотр расположения методов

Метод Ruby имеет только одно определение в одном месте, которое можно определить с помощью Method#source_location. Методы экземпляра класса или модуля могут быть перечислены (как символы) через Class#instance_methods и его варианты с областью (public_, protected_ и private_). Одиночные методы (методы класса a.k.a.) могут быть перечислены через Class#singleton_methods. Передача false в качестве первого аргумента этих методов заставляет их опускать методы, унаследованные от предков. Из одного из этих символов можно получить соответствующий Method через 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.

1.1 Идентификация первичного файла

Идентификация первичного файла для класса из нескольких файлов-кандидатов, полученных с помощью Module#instance_methods и Method#source_location, не является простой проблемой. В общем случае это невозможно.

В приведенном выше примере /tmp/c.rb является интуитивно основным файлом, потому что это первый require d файл, который определяет C. Возможно, именно поэтому в 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 первичное определение для простого класса модели может состоять только из валидаций и других деклараций, оставляя все остальное до рамки. Это определение никогда не будет найдено путем проверки местоположений метода класса.

1.2. Команда pry show-source

Команда pry show-source (a.k.a. $ ) использует вариант этого подхода, применяя свою собственную логику к проверке и сортировке методов и файлов определения классов. См. 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. Конвенция: разрешение автозагрузки общего разрешения Rails

Некоторые классы автоматически загружаются в приложение Rails, но не могут быть детерминистически идентифицированы как принадлежащие к одной из стандартных категорий (модели, контроллеры и т.д.), пути которых зарегистрированы в конфигурации пути Rails. Тем не менее, можно детерминистически идентифицировать файл, содержащий основное определение такого класса. Решение состоит в реализации алгоритма общего алгоритма разрешения автозагрузки, используемого Rails. Пример такой реализации выходит за рамки этого ответа.

3. Анализ статического исходного кода

Если другие подходы неприменимы или недостаточны, можно попытаться применить подход грубой силы: найдите определения данного класса во всех загруженных исходных файлах Ruby. К сожалению, это и ограничено, и сложно.

Файлы, загруженные с помощью Kernel#require, перечислены в $LOADED_FEATURES, так что массив путей можно искать для файлов Ruby, содержащих определение класса. Однако файлы, загруженные с помощью Kernel#load, необязательно перечислены в любом месте, поэтому их нельзя искать. Единственное исключение - файлы, загружаемые с помощью механизма автозагрузки Rails, когда config.cache_classes является ложным (по умолчанию в режиме разработки). В этом случае существует обходное решение: поиск в автозагрузках Rails. Эффективный поиск будет следовать алгоритм разрешения автозагрузки Rails, но также достаточно поискать все пути автозагрузки, которые можно получить с помощью Rails.application.send(:_all_autoload_paths).

Даже для файлов определения классов, которые могут быть перечислены, определение определения данного класса не является тривиальным. Для класса, определенного с помощью оператора class в корневом пространстве имен, это легко: найдите строки, соответствующие /^\s*class\s+#{klass}[\s$]/. Однако для класса, определение которого вложен в тело module или для класса, который динамически определяется с помощью Class::new, для этого требуется разбор каждого файла в абстрактное синтаксическое дерево (AST) и поиск дерева для таких определений. Для класса, который определяется с использованием любого другого генератора классов, поиск AST должен быть проинформирован об этом генераторе. Учитывая, что для любой такой реализации требуется чтение многих файлов с диска, было бы разумно кэшировать все обнаруженные определения классов, если целью является выполнение более одного определения определения класса. Любая такая реализация выходит за рамки этого ответа.

Для определений классов в файлах, которые не соответствуют четко определенным соглашениям, этот подход является наиболее полным. Однако реализация сложна, требует чтения и анализа всех загруженных исходных файлов и по-прежнему в основном ограничена.

Ответ 3

если вы ссылаетесь на модель рельсов и принимаете конфигурацию по умолчанию, она должна находиться в папке моделей приложений, и вы можете получить путь как

File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"