У меня есть ссылка на объект класса данного класса. Мне нужно узнать путь к файлу класса. Класс наследуется от ActiveRecord, если это какая-то помощь.
Любой простой способ сделать это?
Спасибо.
У меня есть ссылка на объект класса данного класса. Мне нужно узнать путь к файлу класса. Класс наследуется от 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_
). Одиночные методы (методы класса 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.
Идентификация первичного файла для класса из нескольких файлов-кандидатов, полученных с помощью 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 первичное определение для простого класса модели может состоять только из валидаций и других деклараций, оставляя все остальное до рамки. Это определение никогда не будет найдено путем проверки местоположений метода класса.
show-source
show-source
(a.k.a. $
) использует вариант этого подхода, применяя свою собственную логику к проверке и сортировке методов и файлов определения классов. См. 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
, необязательно перечислены в любом месте, поэтому их нельзя искать. Единственное исключение - файлы, загружаемые с помощью механизма автозагрузки Rails, когда config.cache_classes
является ложным (по умолчанию в режиме разработки). В этом случае существует обходное решение: поиск в автозагрузках Rails. Эффективный поиск будет следовать алгоритм разрешения автозагрузки Rails, но также достаточно поискать все пути автозагрузки, которые можно получить с помощью Rails.application.send(:_all_autoload_paths)
.
Даже для файлов определения классов, которые могут быть перечислены, определение определения данного класса не является тривиальным. Для класса, определенного с помощью оператора class
в корневом пространстве имен, это легко: найдите строки, соответствующие /^\s*class\s+#{klass}[\s$]/
. Однако для класса, определение которого вложен в тело module
или для класса, который динамически определяется с помощью Class::new
, для этого требуется разбор каждого файла в абстрактное синтаксическое дерево (AST) и поиск дерева для таких определений. Для класса, который определяется с использованием любого другого генератора классов, поиск AST должен быть проинформирован об этом генераторе. Учитывая, что для любой такой реализации требуется чтение многих файлов с диска, было бы разумно кэшировать все обнаруженные определения классов, если целью является выполнение более одного определения определения класса. Любая такая реализация выходит за рамки этого ответа.
Для определений классов в файлах, которые не соответствуют четко определенным соглашениям, этот подход является наиболее полным. Однако реализация сложна, требует чтения и анализа всех загруженных исходных файлов и по-прежнему в основном ограничена.
если вы ссылаетесь на модель рельсов и принимаете конфигурацию по умолчанию, она должна находиться в папке моделей приложений, и вы можете получить путь как
File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"