`autoload` raises an error but `require` does not (ruby)
You'll need to add the 'crack' gem to your $LOAD_PATH by doing:
gem 'crack'
This is necessary because RubyGems replaces Kernel#require with a method that attempts to "activate" the gem before requiring it if necessary, but doesn't do the same thing for Kernel#load - and autoload calls load on the backend.
Ruby autoload conflicts between gmail and parse_resource gems
I had installed the gems with bundler.
To fix the issue with loading both libraries, I installed the gems with the gem command:sudo gem install gmail parse_resource
With this done, I was able to require the libraries in any order, and to connect to gmail and parse without issue.
-Nick
Why the autoload method cannot find the file in my library?
I find the problem is I made a wrong gemspec.
spec.files = Dir["{lib/*,spec/*}"] + %w{README}
It didn't package the file under "lib/renren_api/".
To change it like this will solve this problem.
spec.files = Dir["{lib/**/*,spec/*}"] + %w{README}
Why Rails autoload failed with the file is there?
After debugging into Rails source code, I found the problem.
First, since the helper file is inside helpers
folder, the file should be defined inside Helpers
module:
module API
module Helpers
module BaseHelper
def test
"I am a test helper"
end
end
end
end
Second, API::Helpers::BaseHelper
has the path suffix api/helpers/base_helper
, so make sure "#{Rails.root}/lib/" is inside your autoload path. Then
ActiveSupport` will find it for you.
Rails: Auto-reload gem files used in dummy app
To reload the gem code on file-system changes, I needed to do three steps:
- Unregister the zeitwerk loader defined in
foo_gem.rb
that handles loading the different gem files. - Define a new zeitwerk loader in the
development.rb
of the dummy app that is configured withenable_reloading
. - Setup a file-system watcher and trigger a reload when a gem file changes.
I'm sure there is a simpler and cleaner solution. Feel free to post it as a separate answer. I'll post my solution here, but do consider it a workaround.
Zeitwerk::Loader
in foo_gem.rb
# ~/rails/foo_gem/lib/foo_gem.rb
# require 'foo_gem/bar` # Did not work. Instead:
# (a) use zeitwerk:
require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.push_dir File.join(__dir__)
loader.tag = "foo_gem"
loader.setup
# or (b) use autoload:
module FooGem
autoload :Bar, "foo_gem/bar"
end
Note:
- In the past, I've just loaded all ruby files of the gem with
require
from a kind of index file called just like the gem, here:foo_gem.rb
. This does not work here, because zeitwerk appears to ignore files that have previously been loaded withrequire
. Instead I needed to create a separate zeitwerk loader for the gem. - This loader has no
enable_reloading
because otherwise, reloading would be enabled for this gem whenever using the gem, not just while developing the gem. - I have given the loader a
tag
, which allows to find this loader later in theZeitwerk::Registry
in order to un-register it. - Instead of using zeitwerk in
foo_gem.rb
, one could also useautoload
there like the devise gem does. This is the best way if one wants to support rails versions earlier than 6 because zeitwerk requires rails 6+. Usingautoload
here also makes step 1 in the next section unnecessary.
Zeitwerk::Loader in development.rb
of the dummy app
# ~/rails/foo_gem/spec/dummy_app/config/environments/development.rb
# 1. Unregister the zeitwerk loader defined in foo_gem.rb that handles loading
# the different gem files.
#
Zeitwerk::Registry.loaders.detect { |l| l.tag == "foo_gem" }.unregister
# 2. Define a new zeitwerk loader in the development.rb of the dummy app
# that is configured with enable_reloading.
#
gem_root_path = Pathname.new(File.expand_path(Rails.root.join("../..")))
gem_loader = Zeitwerk::Loader.new
gem_loader.push_dir gem_root_path.join("lib")
gem_loader.enable_reloading
gem_loader.setup
# 3. Setup a file-system watcher and trigger a reload when a gem file changes.
#
Listen.to gem_root_path.join("lib"), only: /\.rb$/ do
gem_loader.reload
end.start
Note:
- Zeitwerk does not allow two loaders managing the same files. Therefore, I need to unregister the previously defined loader tagged
"foo_gem"
. - The new loader used in the dummy app has
enable_reloading
. Therefore, when using the dummy app withrails server
,rails console
, or when running the specs, the gem files can be reloaded. - The gem files are not automatically reloaded by zeitwerk. One needs a file-system watcher to trigger the reload on file-system changes. I did not manage to get the
ActiveSupport::FileUpdateChecker
working. Instead, I've used the listen gem as file-system watcher.
With this setup, when using the rails server
, the rails console
of the dummy app or integration tests using the dummy app, gem files are reloaded after being edited, which means that one does no longer need to restart the rails server
to pick up the changes.
References
Zeitwerk::Loader
Zeitwerk::Registry
- https://github.com/fxn/zeitwerk
- https://guides.rubyonrails.org/classic_to_zeitwerk_howto.html
- https://guides.rubyonrails.org/autoloading_and_reloading_constants.html
Zeitwerk: Add engine/gem directories to autoload path of parent Rails app
Rails sets up two loaders main
and once
:
Rails.autoloaders.main
Rails.autoloaders.once
These are just instances of Zeitwerk::Loader
. Rails also gives you a config to add root directories to these loaders:
config.autoload_paths # main
config.autoload_once_paths # once
When gem's lib
directory is added to autoload through one of these configs, lib becomes a root directory:
# config.autoload_paths += paths["lib"].to_a
>> Rails.autoloaders.main.root_dirs
=>
...
"/home/alex/code/stackoverflow/my_engine/lib"=>Object,
...
When a class from the gem is called, zeitwerk uses registered loaders to look up and to load the file corresponding to this class.
If the gem then sets up its own loader:
require "zeitwerk"
loader = Zeitwerk::Loader.for_gem
loader.setup
another instance of Zeitwerk::Loader
is created with its own root directories:
>> Zeitwerk::Registry.loaders.detect { |z| z.tag == "my_engine" }
=>
#<Zeitwerk::GemLoader:0x00007fe5e53e0f80
...
@root_dirs={"/home/alex/code/stackoverflow/my_engine/lib"=>Object},
...
# NOTE: these are the two loaders registered by rails
>> Zeitwerk::Registry.loaders.select { |z| z.tag =~ /rails/ }.count
=> 2
Zeitwerk doesn't allow two loaders to have a shared directory and raises an error showing two conflicting loaders.
Because the gem is a Rails::Engine the best option is to let rails manage zeitwerk loaders and remove Zeitwerk::Loader.for_gem setup.
# only use rails config
config.autoload_paths += paths["lib"].to_a
On the other hand, gem loader is already set up and config.autoload_paths is not needed.
# NOTE: without any loaders
>> MyEngine::Test
# (irb):1:in `<main>': uninitialized constant MyEngine::Test (NameError)
# MyEngine::Test
# ^^^^^^
# NOTE: with gem loader
#
# require "zeitwerk"
# loader = Zeitwerk::Loader.for_gem
# loader.setup
#
>> MyEngine::Test
=> MyEngine::Test
# NOTE: with rails `main` loader
#
# config.autoload_paths += paths["lib"].to_a
#
>> MyEngine::Test
=> MyEngine::Test
# NOTE: with gem loader and rails loader
$ bin/rails c
# /home/alex/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/zeitwerk-2.6.0/lib/zeitwerk/loader.rb:480:in
# `block (3 levels) in raise_if_conflicting_directory':
# loader (Zeitwerk::Error)
Update
# Use rails loaders
# config.autoload_path .-> Zeitwerk::Loader(@tag=rails.main)
# config.autoload_once_path |-> Zeitwerk::Loader(@tag=rails.once)
# |
# Or create a new loader |
# Zeitwerk::Loader.for_gem |-> Zeitwerk::GemLoader(@tag=my_engine)
# |
# my_engine/lib can only be in one of these
Zeitwerk does the loading and reloading. Rails is just another gem here.
If you don't use rails config, Zeitwerk will find files through Zeitwerk::GemLoader(@tag=my_engine)
, that the gem has created.
If you use rails config, Zeitwerk will find files through Zeitwerk::Loader(@tag=rails.main)
, that rails has created (making GemLoader unnecessary).
If lib is a root directory in any of the existing loaders there is no need to have any requires or autoloads for files in lib directory. Except for things that are needed before Zeitwerk kicks in, like MyEngine::Engine from lib/my_engine/engine.rb.
Related Topics
How to Distribute a Ruby Script via Homebrew
Ruby: Why Are All Coordinates Getting Updated in an Array of Arrays
Ruby String Split into Words Ignoring All Special Characters: Simpler Query
I Trying to Make a Code That Gives the User a Personal Number After They Have Made an User
Defined' and 'Unless' Not Working as Expected
Ruby: Wait for All Threads Completed Using Join and Threadswait.All_Waits - What the Difference
Class VS Class.New, Module VS Module.New
How to Collapse Double Splat Arguments into Nothing
How to Use Multiple Models for Tag_Cloud
Symbol#To_Proc Shorthand with the Stabby Lambda Syntax
How to Sort So That "Vitamin B12" Is Not in Front of "Vitamin B6"
Rails, Ruby, How to Count and Sort, Display Top Results
Open Xml File with Nokogiri Update Node and Save
Ruby Popen3 -- How to Repeatedly Write to Stdin & Read Stdout Without Re-Opening Process
Ruby Range: Operators in Case Statement
How to Efficiently Extract Repeated Elements in a Ruby Array
Spring Doesn't Work. [ Uninitialized Constant Spring::Sid::Dl ]