I have a reference to the Class object of a given class. I need to find out the file path of the class. The class inherits from ActiveRecord if that is any help.
Any easy way of doing this?
Thanks.
I have a reference to the Class object of a given class. I need to find out the file path of the class. The class inherits from ActiveRecord if that is any help.
Any easy way of doing this?
Thanks.
Use source_location
on its methods:
YourClass.instance_methods(false).map { |m|
YourClass.instance_method(m).source_location.first
}.uniq
You might get more than one location, as methods might be defined in different places.
There is no way of doing this that works for all class definitions. There are some straightforward ways that work for some cases, and there are some complicated ways that work for other cases.
In Ruby, classes and modules can be reopened for additional definitions (monkey patched) many times. There is no built-in notion of a primary definition for a class or module. There is also no built-in way to list all of the files that contribute to the definition of a class. However, there is a built-in way to list the files that define methods within a class. To find the static definitions that contribute other components (constants, declarations, etc.), one can follow known conventions (if applicable) or apply static source code analysis.
A Ruby method only has one definition in one location, which can be determined via Method#source_location
. The instance methods of a class or module can be listed (as symbols) via Class#instance_methods
and its scoped (public_
, protected_
, and private_
) variants. The singleton methods (a.k.a. class methods) can be listed via Class#singleton_methods
. Passing false
as the first argument to these methods causes them to omit methods inherited from ancestors. From one of these symbols, one can obtain the corresponding Method
via Class#instance_method
, then use Method#source_location
to obtain the file and line number of the method. This works for methods defined statically (using def
) or dynamically (using various means, such as Module#class_eval
combined with Module#define_method
).
For example, consider these files that define a module M
and a class 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
Given these definitions, one can inspect the class and find its source files as the following Pry session demonstrates.
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"]
The order of values returned by Module#instance_methods
isn't specified in the documentation and varies between Ruby versions.
Identifying the primary file for a class among multiple candidate files obtained via Module#instance_methods
and Method#source_location
is not a simple problem. In the general case, it is impossible.
In the example above, /tmp/c.rb
is intuitively the primary file because it is the first require
d file that defines C
. Perhaps this is why, in Ruby 2.3.3 and 2.4.0, Module#instance_methods
lists its methods first. However, as mentioned above, the order is undocumented, and it varies between Ruby versions. Notice that the first method defined in C
, in order of execution, is #ifoo
. Incidentally, with Ruby 1.9.3 through 2.2.6, the first item of instance_methods_syms
is :ifoo
, and the first item class_files
is therefore /tmp/m.rb
—clearly not what anyone would intuitively consider the primary file for C
.
Furthermore, consider what happens if we remove the method definitions from /tmp/c.rb
, leaving only the declarative-style calls extend M
, has_ibar
, and has_cbar
. In this case, /tmp/c.rb
is totally absent from the class_files
. This is not an unrealistic scenario. For example, in Active Record, the primary definition for a simple model class may consist only of validations and other declarations, leaving everything else up to the framework. This definition will never be found by inspecting the class's method locations.
show-source
Pry's show-source
(a.k.a. $
) command uses a variant of this approach, applying its own logic to the inspection and sorting of methods and class definition files. See Pry::WrappedModule
and Pry::Method
if you're curious. It works rather well in practice, but because it relies on Method#source_location
, it is unable to find class definitions that do not define methods.
This approach applies only to scenarios where the class being inspected is defined according to some well-defined conventions. If you know that the class you're inspecting follows such a convention, then you can use it to find its primary definition with certainty.
This approach works even when the method location approach fails—that is, when the primary definition does not contain any method definitions. However, it is limited to class definitions that follow well-defined conventions.
In a simple Rails application, the application's model classes are defined within its app/models
directory, with a file path that can be derived deterministically from the class name. Given such a model class klass
, the file that contains its primary definition is at the following location:
Rails.root.join('app', 'models', "#{klass.name.underscore}.rb").to_s
For example, a model class ProductWidget
would be defined in APP_ROOT/app/models/product_widget.rb
, where APP_ROOT
is the application's root directory path.
To generalize this, one must consider extensions of the simple Rails configuration. In a Rails application that defines custom paths for model definitions, one must consider all of them. Also, since an arbitrary model class may be defined in any Rails engine loaded by the application, one must also look in all loaded engines, taking into account their custom paths. The following code combines these considerations.
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) }
This example applies specifically to Rails models, but it can be easily adapted to controllers and other classes whose definition locations are subject to the Rails conventions and configuration.
Some classes are autoloaded in a Rails application but cannot be deterministically identified as belonging to one of the standard categories (models, controllers, etc.) whose paths are registered in the Rails path configuration. Still, it is possible to deterministically identify the file containing the primary definition of such a class. The solution is to implement the generic autoload resolution algorithm used by Rails. An example of such an implementation is beyond the scope of this answer.
If other approaches are not applicable or insufficient, one might try to resort to the brute force approach: look for definitions of the given class in all loaded Ruby source files. Unfortunately, this is both limited and complicated.
Files that are loaded using Kernel#require
are listed in $LOADED_FEATURES
, so that array of paths can be searched for Ruby files that contain a definition of the class. However, files that are loaded using Kernel#load
are not necessarily listed anywhere, so they cannot be searched. One exception to this is files that are loaded via the Rails autoload mechanism when config.cache_classes
is false (the default in development mode). In this case, there is a workaround: search in the Rails autoload paths. An efficient search would follow the Rails autoload resolution algorithm, but it is also sufficient to search all autoload paths, which can be obtained via Rails.application.send(:_all_autoload_paths)
.
Even for class definition files that can be listed, identifying the definition of a given class is not trivial. For a class that is defined with a class
statement in the root namespace, this is easy: find lines that match /^\s*class\s+#{klass}[\s$]/
. However, for a class whose definition is nested in a module
body, or for a class that is defined dynamically using Class::new
, this requires parsing each file into an abstract syntax tree (AST) and searching the tree for such definitions. For a class that is defined using any other class generator, the AST search needs to be made aware of that generator. Considering that any such implementation requires reading many files from disk, it would be prudent to cache all discovered class definitions if the intent is to perform more than one class definition lookup. Any such implementation is beyond the scope of this answer.
For class definitions in files that do not follow well-defined conventions, this approach is the most thorough one. However, the implementation is complex, requires reading and parsing all loaded source files, and is still fundamentally limited.
if you are referring to rails model and take the default configuration it should be in app models folder and you could get a path as
File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"