Running the app
Time to try out ruby-explorer on our Rails app:
jmax@deepthought ~/projects/ruby-explorer $ bin/ruby-explorer ../bare-rails
jmax@deepthought ~/projects/ruby-explorer $ load(["/home/jmax/projects/bare-rails/bin/spring"])
require(["rubygems"])
require(["rubygems"]): returned false
require(["bundler"])
require(["bundler/compatibility_guard"])
require(["rubygems"])
require(["rubygems"]): returned false
require(["bundler/version"])
require(["rubygems"])
require(["rubygems"]): returned false
:
:
require(["spring/configuration"])
require(["spring/configuration"]): returned false
load(["/home/jmax/projects/bare-rails/bin/rails"])
load(["/home/jmax/projects/bare-rails/bin/spring"])
load(["/home/jmax/projects/bare-rails/bin/spring"]): returned true
require_relative(["../config/boot"])
Traceback (most recent call last):
28: from bin/rails:3:in `<main>'
27: from /home/jmax/projects/ruby-explorer/src/probe.rb:28:in `block in wrap'
26: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `block in install'
25: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `call'
24: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `load'
23: from /home/jmax/projects/bare-rails/bin/spring:15:in `<top (required)>'
22: from /home/jmax/projects/ruby-explorer/src/probe.rb:28:in `block in wrap'
21: from /home/jmax/projects/ruby-explorer/src/probe.rb:5:in `block in install'
20: from /home/jmax/projects/ruby-explorer/src/probe.rb:5:in `call'
19: from /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
18: from /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
17: from /home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
16: from /home/jmax/projects/ruby-explorer/src/probe.rb:28:in `block in wrap'
15: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `block in install'
14: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `call'
13: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `load'
12: from /home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
11: from /home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
10: from /home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
9: from /home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
8: from /home/jmax/projects/ruby-explorer/src/probe.rb:28:in `block in wrap'
7: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `block in install'
6: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `call'
5: from /home/jmax/projects/ruby-explorer/src/probe.rb:19:in `load'
4: from /home/jmax/projects/bare-rails/bin/rails:8:in `<top (required)>'
3: from /home/jmax/projects/ruby-explorer/src/probe.rb:28:in `block in wrap'
2: from /home/jmax/projects/ruby-explorer/src/probe.rb:12:in `block in install'
1: from /home/jmax/projects/ruby-explorer/src/probe.rb:12:in `call'
/home/jmax/projects/ruby-explorer/src/probe.rb:12:in `require_relative': cannot load such file -- /home/jmax/projects/ruby-explorer/config/boot (LoadError)
Works great. Right up until the first call
to require_relative
, when it blows up. Our problem is
that require_relative
looks for the specified file
relative to the file that it's called from. And our wrapper calls the
original require_relative
from a different file than
our wrapper was called from. Bah. How annoying.
Faking require_relative
How to deal with this? Well, if we can figure out what file our
wrapper is getting called from, then we can find the file ourselves
and call require
. Amazingly, Ruby actually lets us do
that. The caller
method gives us a copy of the call
stack. Sticking a debug print into our require_relative
wrapper, so we can see exactly what we have, like this:
wrap(Kernel, :require_relative) do |original_require_relative, args|
# DEBUG
puts caller
puts "require_relative(#{args})"
return_value= original_require_relative.call(*args)
puts "require_relative(#{args}): returned #{return_value}"
return_value
end
We get the result:
/home/jmax/projects/ruby-explorer/src/probe.rb:31:in `block in wrap'
/home/jmax/projects/bare-rails/bin/rails:8:in `<top (required)>'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `load'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `call'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `block in install'
/home/jmax/projects/ruby-explorer/src/probe.rb:31:in `block in wrap'
/home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client/rails.rb:28:in `call'
/home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client/command.rb:7:in `call'
/home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/client.rb:30:in `run'
/home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/bin/spring:49:in `<top (required)>'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `load'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `call'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `block in install'
/home/jmax/projects/ruby-explorer/src/probe.rb:31:in `block in wrap'
/home/jmax/.rvm/gems/ruby-2.5.5/gems/spring-2.1.0/lib/spring/binstub.rb:11:in `<top (required)>'
/home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/rubygems/core_ext/kernel_require.rb:54:in `require'
/home/jmax/projects/ruby-explorer/src/probe.rb:5:in `call'
/home/jmax/projects/ruby-explorer/src/probe.rb:5:in `block in install'
/home/jmax/projects/ruby-explorer/src/probe.rb:31:in `block in wrap'
/home/jmax/projects/bare-rails/bin/spring:15:in `<top (required)>'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `load'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `call'
/home/jmax/projects/ruby-explorer/src/probe.rb:22:in `block in install'
/home/jmax/projects/ruby-explorer/src/probe.rb:31:in `block in wrap'
bin/rails:3:in `<main>'
The first entry is us; when we call caller
, we
push a frame onto the stack, and caller
dutifully
reports that to us. The second entry is in the middle
of wrap
. The third one is the code that called our
wrapper, and the one we're interested in. We can pass a couple
arguments to caller
in case we're only interested in
part of the stack, as here. By changing our call
to caller(2,1)
, we can get just the frame we're
interested in.
The return value from caller
is still an array,
even though we only asked for one entry. Using first
,
we can get the first (and only) element of the array. We could do the
same thing with a subscript, but I think using first
is
slightly clearer. We can then use split
and another
call to first
to get the actual path of our
caller. Putting it all together, we
use caller.first.split(":").first
- and we have the
full path of the file that called our wrapper.
File path in hand, our wrapper
for require_relative
becomes:
wrap(Kernel, :require_relative) do |original_require_relative, args|
puts "require_relative(#{args})"
caller_path= caller(2,1).first.split(":").first
base_directory= Pathname.new(caller_path).dirname
absolute_file= "#{base_directory}/#{args.first}"
return_value= require(absolute_file)
puts "require(#{absolute_file}): returned #{return_value}"
return_value
end
And when we try running ruby-explorer again, we get:
jmax@deepthought ~/projects/ruby-explorer $ bin/ruby-explorer ../bare-rails
jmax@deepthought ~/projects/ruby-explorer $ load(["/home/jmax/projects/bare-rails/bin/spring"])
require(["rubygems"])
require(["rubygems"]): returned false
require(["bundler"])
require(["bundler/compatibility_guard"])
require(["rubygems"])
require(["rubygems"]): returned false
require(["bundler/version"])
require(["rubygems"])
require(["rubygems"]): returned false
:
:
require_relative(["../config/boot"])
require(["/home/jmax/projects/bare-rails/bin/../config/boot"])
:
:
=> Booting Puma
=> Rails 6.0.2.2 application starting in development
=> Run `rails server --help` for more startup options
:
:
load(["/home/jmax/.rvm/gems/ruby-2.5.5/gems/actionmailbox-6.0.2.2/config/routes.rb", false])
load(["/home/jmax/.rvm/gems/ruby-2.5.5/gems/actionmailbox-6.0.2.2/config/routes.rb", false]): returned true
load(["/home/jmax/.rvm/gems/ruby-2.5.5/gems/activestorage-6.0.2.2/config/routes.rb", false])
load(["/home/jmax/.rvm/gems/ruby-2.5.5/gems/activestorage-6.0.2.2/config/routes.rb", false]): returned true
Puma starting in single mode...
* Version 4.3.3 (ruby 2.5.5-p157), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
Our target app started! Success! Or is it?
Another thing to cope with
Thinking back to our original problem, we remember that it was
"I want to know all of the files loaded by a Rails project."
Have we done that? Sadly, no. Look at the very first call
to require
in our output. We report that we've been
asked to require("rubygems")
. Where is that file
located?
To answer that, we need to dig into the details of the functions
we're wrapping. The easiest case
is require_relative
. It simply looks for the specified
file relative to the current file.
Life is more complicated for require
and load
. In those cases, the code looks at the first
character of the filename. If it's '/', '~', or '.', then it just
looks for the file as specified, as an absolute path, a path relative
to the current user's home directory, or relative to the current
directory, respectively. Pretty simple.
But most of the calls to require
in our output so
far aren't like that. They look like, for
example, require('bundler')
. In these cases, Ruby will
use the global variable $LOAD_PATH
, which consists of a
list of directories. Ruby checks each of them, in order, looking to
see if (in our example) there's a file called bundler
, bundler.rb
, or bundler.so
. If so, it loads that file. If not, it
checks the next directory.
Just to make life even more interesting,
in the case of require
, RubyGems gets into the act as well. It
actually does something like what we're doing; it replaces Ruby's
require
with it's own version. Its version searches over
the $LOAD_PATH
, as described above. If it doesn't find
the file that way, then it searches the installed gems for the
file. If it finds the file, it activates the gem that it
found the file in.
What 'activates' means in this context is that the gem's
directories are added to $LOAD_PATH
. This is a little
vague, because it's actually the gem's code which adds entries to
$LOAD_PATH. Usually, just the gem's lib directory will be
added, but that's only a convention, so we can't rely on it.
This so-called 'RubyGems hack' is actually the mechanism that resolves most of our calls, so we can't neglect it.
So, how do we deal with all this?
Checking $LOAD_PATH
isn't too bad; we can implement
that. Duplicating the RubyGems hack, on the other hand, would require
repeating a significant chunk of RubyGems' code. Worse, some of the
selected gem's code would likely get executed twice; once by RubyGems,
and once by us.
We're saved by a few things:
- First, when we
require
a file,require
returns false if the file was already loaded. Remember thatrequire
promises to never load the same file twice. So if our call-through to the original version ofrequire
returnsfalse
, we're done; no additional file was loaded by the call. - Second,
require_relative
makes the same promise; if it returnsfalse
, we're done. - Third, the way in which
require
andrequire_relative
implement that feature is helpful to us. They do it by stashing the absolute path of each file pulled in byrequire
orrequire_relative
in the array$LOADED_FEATURES
. So ifrequire
orrequire_relative
returnstrue
, we can search$LOADED_FEATURES
to pick up the absolute path of the loaded file.
In summary, for require
and require_relative
, we can
check $LOADED_FEATURES
after the
call. For load
, we can see if the file exists relative
to the current directory, and if not,
check $LOAD_PATH
.
Finding require
'd files
So, let's implement tests for all
this. Taking require
and require_relative
first, it seems good to implement the code for
finding the actual file loaded in a new
method, require_file(file, loaded_features)
. The first argument is the filename passed to require
or require_relative
, and the second
is $LOADED_FEATURES
. This brings out an interesting
feature of TDD; we have that second argument to make testing easier,
but it's actually a better design; the method isn't coupled to a
global variable any more.
Our tests for it are:
describe "#require_file" do
it "doesn't find a file that hasn't been loaded" do
expect(the_probe.require_file("a-file", [])).to be_nil
end
it "finds a loaded file with no path and no extension when the loaded file has a .rb extension" do
expect(the_probe.require_file("a-file", ["/some/path/wrong-file.rb", "/some/path/a-file.rb"])).
to eq("/some/path/a-file.rb")
end
it "finds a loaded file with no path and no extension when the loaded file has a .so extension" do
expect(the_probe.require_file("a-file", ["/some/path/wrong-file.rb", "/some/path/a-file.so"])).
to eq("/some/path/a-file.so")
end
it "finds a loaded file with no path and an .rb extension when the loaded file has a .rb extension" do
expect(the_probe.require_file("a-file.rb", ["/some/path/wrong-file.rb", "/some/path/a-file.rb"])).
to eq("/some/path/a-file.rb")
end
it "finds a loaded file with no path and an .so extension when the loaded file has a .so extension" do
expect(the_probe.require_file("a-file.so", ["/some/path/wrong-file.rb", "/some/path/a-file.so"])).
to eq("/some/path/a-file.so")
end
it "finds a loaded file with a path and no extension when the loaded file has a .rb extension" do
expect(the_probe.require_file("../a-file", ["/some/path/wrong-file.rb", "/some/path/a-file.rb"])).
to eq("/some/path/a-file.rb")
end
it "finds a loaded file with a path and no extension when the loaded file has a .so extension" do
expect(the_probe.require_file("../a-file", ["/some/path/wrong-file.rb", "/some/path/a-file.so"])).
to eq("/some/path/a-file.so")
end
it "finds a loaded file with a path and an .rb extension when the loaded file has a .rb extension" do
expect(the_probe.require_file("../a-file.rb", ["/some/path/wrong-file.rb", "/some/path/a-file.rb"])).
to eq("/some/path/a-file.rb")
end
it "finds a loaded file with a path and an .so extension when the loaded file has a .so extension" do
expect(the_probe.require_file("../a-file.so", ["/some/path/wrong-file.rb", "/some/path/a-file.so"])).
to eq("/some/path/a-file.so")
end
it "finds the last file loaded" do
expect(the_probe.require_file("a-file", ["/some/path/a-file.rb", "/some/other/path/a-file.rb"])).
to eq("/some/other/path/a-file.rb")
end
end
And the implementation is:
def require_file(file, loaded_features)
base_file= Pathname.new(file).basename.to_s
if base_file =~ /.*(\.rb|\.so)$/
match_pattern= /.*\/#{base_file}$/
else
match_pattern= /.*\/#{base_file}(\.rb|\.so)$/
end
loaded_features.reverse_each do |loaded_file|
return loaded_file if loaded_file =~ match_pattern
end
nil
end
We first use Pathname#basename
strip off any
directory component of the requested filename, so we're left with just
the filename. Then, we generate a regex from the filename. If the
filename already has an extension, then we use a regex which will
match exactly that filename, with any directory path in front of
it. If, on the other hand, the requested filename doesn't already have
an extension, then we use a regex which will match either a .so or a
.rb file, again, with any directory path in front of it.
Having set up our regex to match the file we're looking for, we
then scan through $LOADED_FEATURES
for our file. We
start at the end of the array and search backward, because entries are
added at the end, so we'll find the one we want more quickly.
And that's the hard work for require
and require_relative
sorted out.
Finding load
'd files
Turning to load
next, we follow a similar design. This time,
our new method is load_file(filename, load_path).
Our tests look like:
describe "#load_file" do
before do
FileUtils.mkdir_p "subdir"
File.open("subdir/a-file.rb", "w") {|f| f.puts("'junk'")}
end
after do
FileUtils.rm_rf "subdir"
end
it "finds the file when the filename is a non-absolute path" do
expect(the_probe.load_file("subdir/a-file.rb", [])).
to eq("#{Dir.pwd}/subdir/a-file.rb")
end
it "finds the file on the $LOAD_PATH from a non-absolute path" do
expect(the_probe.load_file("a-file.rb", ["#{Dir.pwd}/subdir"])).
to eq("#{Dir.pwd}/subdir/a-file.rb")
end
it "finds the file from an absolute path" do
expect(the_probe.load_file("#{Dir.pwd}/subdir/a-file.rb", [])).
to eq("#{Dir.pwd}/subdir/a-file.rb")
end
end
And the implementation is:
def load_file(filename, load_path)
loaded_file= File.expand_path(filename)
if File.exists?(loaded_file)
return loaded_file
end
load_path.each do |path|
loaded_file= "#{path}/#{filename}"
if File.exists?(loaded_file)
return loaded_file
end
end
nil
end
The actual code to generate our output messages is verbose enough that it clutters the wrappers and makes them harder to follow. So we add two small utility methods which move the details of generating our output messages out of the wrapper:
def require_message(loaded, file, loaded_features)
absolute_file= require_file(file, loaded_features)
if !loaded
"already loaded"
elsif absolute_file
"loaded #{absolute_file}"
else
"couldn't find loaded file"
end
end
def load_message(filename, load_path)
found_file= load_file(filename, load_path)
if found_file
"loaded #{found_file}"
else
"couldn't find file"
end
end
And our install
method, with all three wrappers, ends up looking like:
def install
wrap(Kernel, :require) do |original_require, args|
return_value= original_require.call(*args)
puts "require(#{args.first}): #{require_message(return_value, args.first, $LOADED_FEATURES)}"
return_value
end
wrap(Kernel, :require_relative) do |original_require_relative, args|
return_value= require("#{caller_directory}/#{args.first}")
puts "require_relative(#{args.first}): #{require_message(return_value, args.first, $LOADED_FEATURES)}"
return_value
end
wrap(Kernel, :load) do |original_load, args|
return_value= original_load.call(*args)
puts "load(#{args.first}): #{load_message(args.first, $LOAD_PATH)}"
return_value
end
end
Running the app: One More Time
So, let's try running our target app again:
jmax@deepthought $ bin/ruby-explorer ../bare-rails/
require(rubygems): already loaded
require(rubygems): already loaded
require(rubygems): already loaded
require(bundler/version): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/version.rb
require(bundler/compatibility_guard): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/compatibility_guard.rb
require(etc): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/2.5.0/x86_64-linux/etc.so
require(bundler/vendor/fileutils/lib/fileutils): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/vendor/fileutils/lib/fileutils.rb
require(bundler/vendored_fileutils): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/vendored_fileutils.rb
:
:
require(bundler/remote_specification): already loaded
require(bundler/stub_specification): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/stub_specification.rb
require(bundler/endpoint_specification): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/endpoint_specification.rb
require(bundler/setup): loaded /home/jmax/.rvm/rubies/ruby-2.5.5/lib/ruby/site_ruby/2.5.0/bundler/setup.rb
require_relative(bootsnap/version): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/version.rb
require_relative(bootsnap/bundler): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/bundler.rb
require_relative(../explicit_require): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/explicit_require.rb
require_relative(load_path_cache/path_scanner): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/path_scanner.rb
require_relative(path_scanner): already loaded
require_relative(load_path_cache/path): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/path.rb
require_relative(../explicit_require): already loaded
require_relative(load_path_cache/cache): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/cache.rb
require_relative(../explicit_require): already loaded
require_relative(load_path_cache/store): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/store.rb
require_relative(load_path_cache/change_observer): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/change_observer.rb
require_relative(load_path_cache/loaded_features_index): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/loaded_features_index.rb
require_relative(load_path_cache/realpath_cache): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/realpath_cache.rb
require_relative(bootsnap/load_path_cache): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache.rb
require_relative(bootsnap/compile_cache): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/compile_cache.rb
require_relative(../bootsnap): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap.rb
require_relative(load_path_cache/core_ext/kernel_require): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/bootsnap-1.4.6/lib/bootsnap/load_path_cache/core_ext/kernel_require.rb
require(/home/jmax/projects/bare-rails/bin/../config/boot): loaded /home/jmax/projects/bare-rails/config/boot.rb
require_relative(../config/boot): loaded /home/jmax/projects/bare-rails/config/boot.rb
=> Booting Puma
=> Rails 6.0.2.2 application starting in development
=> Run `rails server --help` for more startup options
load(/home/jmax/projects/bare-rails/config/initializers/application_controller_renderer.rb): loaded /home/jmax/projects/bare-rails/config/initializers/application_controller_renderer.rb
load(/home/jmax/projects/bare-rails/config/initializers/assets.rb): loaded /home/jmax/projects/bare-rails/config/initializers/assets.rb
load(/home/jmax/projects/bare-rails/config/initializers/backtrace_silencers.rb): loaded /home/jmax/projects/bare-rails/config/initializers/backtrace_silencers.rb
load(/home/jmax/projects/bare-rails/config/initializers/content_security_policy.rb): loaded /home/jmax/projects/bare-rails/config/initializers/content_security_policy.rb
load(/home/jmax/projects/bare-rails/config/initializers/cookies_serializer.rb): loaded /home/jmax/projects/bare-rails/config/initializers/cookies_serializer.rb
load(/home/jmax/projects/bare-rails/config/initializers/filter_parameter_logging.rb): loaded /home/jmax/projects/bare-rails/config/initializers/filter_parameter_logging.rb
load(/home/jmax/projects/bare-rails/config/initializers/inflections.rb): loaded /home/jmax/projects/bare-rails/config/initializers/inflections.rb
load(/home/jmax/projects/bare-rails/config/initializers/mime_types.rb): loaded /home/jmax/projects/bare-rails/config/initializers/mime_types.rb
load(/home/jmax/projects/bare-rails/config/initializers/wrap_parameters.rb): loaded /home/jmax/projects/bare-rails/config/initializers/wrap_parameters.rb
load(/home/jmax/projects/bare-rails/config/routes.rb): loaded /home/jmax/projects/bare-rails/config/routes.rb
load(/home/jmax/.rvm/gems/ruby-2.5.5/gems/actionmailbox-6.0.2.2/config/routes.rb): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/actionmailbox-6.0.2.2/config/routes.rb
load(/home/jmax/.rvm/gems/ruby-2.5.5/gems/activestorage-6.0.2.2/config/routes.rb): loaded /home/jmax/.rvm/gems/ruby-2.5.5/gems/activestorage-6.0.2.2/config/routes.rb
Puma starting in single mode...
* Version 4.3.3 (ruby 2.5.5-p157), codename: Mysterious Traveller
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://127.0.0.1:3000
* Listening on tcp://[::1]:3000
Use Ctrl-C to stop
And there we are! A full trace of every file pulled into our target Rails app, with full file paths for every file.
Phew!