Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce config.autoload_lib #48572

Merged
merged 1 commit into from
Jun 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions guides/source/autoloading_and_reloading_constants.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,47 @@ WARNING: You cannot autoload code in the autoload paths while the application bo

The autoload paths are managed by the `Rails.autoloaders.main` autoloader.

config.autoload_lib(ignore:)
----------------------------

By default, the `lib` directory does not belong to the autoload paths of applications or engines.

The configuration method `config.autoload_lib` adds the `lib` directory to `config.autoload_paths` and `config.eager_load_paths`. It has to be invoked from `config/application.rb` or `config/environments/*.rb`, and it is not available for engines.

Normally, `lib` has subdirectories that should not be managed by the autoloaders. Please, pass their name relative to `lib` in the required `ignore` keyword argument. For example:

```ruby
config.autoload_lib(ignore: %w(assets tasks))
```

Why? While `assets` and `tasks` share the `lib` directory with regular code, their contents are not meant to be autoloaded or eager loaded. `Assets` and `Tasks` are not Ruby namespaces there. Same with generators if you have any:

```ruby
config.autoload_lib(ignore: %w(assets tasks generators))
```

`config.autoload_lib` is not available before 7.1, but you can still emulate it as long as the application uses Zeitwerk:

```ruby
# config/application.rb
module MyApp
class Application < Rails::Application
lib = Rails.root.join("lib")

config.autoload_paths << lib
config.eager_load_paths << lib

Rails.autoloaders.main.ignore(
lib.join("assets"),
lib.join("tasks"),
lib.join("generators")
)

...
end
end
```

config.autoload_once_paths
--------------------------

Expand Down
12 changes: 12 additions & 0 deletions guides/source/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,18 @@ Accepts an array of paths from which Rails will autoload constants that won't be

Accepts an array of paths from which Rails will autoload constants. Default is an empty array. Since [Rails 6](upgrading_ruby_on_rails.html#autoloading), it is not recommended to adjust this. See [Autoloading and Reloading Constants](autoloading_and_reloading_constants.html#autoload-paths).

#### `config.autoload_lib(ignore:)`

This method adds `lib` to `config.autoload_paths` and `config.eager_load_paths`.

Normally, the `lib` directory has subdirectories that should not be autoloaded or eager loaded. Please, pass their name relative to `lib` in the required `ignore` keyword argument. For example,

```ruby
config.autoload_lib(ignore: %w(assets tasks generators))
```

Please, see more details in the [autoloading guide](autoloading_and_reloading_constants.html).

#### `config.beginning_of_week`

Sets the default beginning of week for the
Expand Down
13 changes: 13 additions & 0 deletions railties/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
* The new method `config.autoload_lib(ignore:)` provides a simple way to
autoload from `lib`:

```ruby
# config/application.rb
config.autoload_lib(ignore: %w(assets tasks))
```

Please, see further details in the [autoloading
guide](https://guides.rubyonrails.org/v7.1/autoloading_and_reloading_constants.html).

*Xavier Noria*

* Don't show secret_key_base for `Rails.application.config#inspect`.

Before:
Expand Down
13 changes: 13 additions & 0 deletions railties/lib/rails/application/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "ipaddr"
require "active_support/core_ext/array/wrap"
require "active_support/core_ext/kernel/reporting"
require "active_support/file_update_checker"
require "active_support/configuration_file"
Expand Down Expand Up @@ -449,6 +450,18 @@ def database_configuration
raise e, "Cannot load database configuration:\n#{e.message}", e.backtrace
end

def autoload_lib(ignore:)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fxn Maybe by calling this method, we can also remove lib from Ruby's load path

def self.add_lib_to_load_path!(root) # :nodoc:
**

only if this option is set to false

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chaadow good call. I think this one is tricky:

  • People may expect lib to be in $LOAD_PATH because it is generally there in Ruby projects.
  • Rails issues a require to load generators on demand. lib/generators is idiomatic and you'd expect that to work as it did until now.

Let me think about it. Even if we special-case it, it'd need some docs and tests, so the comment was useful in either case, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end, I opted for documenting and adding test coverage for it (#48596). Thanks!

lib = root.join("lib")

# Set as a string to have the same type as default autoload paths, for
# consistency.
autoload_paths << lib.to_s
eager_load_paths << lib.to_s

ignored_abspaths = Array.wrap(ignore).map { lib.join(_1) }
Rails.autoloaders.main.ignore(ignored_abspaths)
end

def colorize_logging
ActiveSupport::LogSubscriber.colorize_logging
end
Expand Down
70 changes: 70 additions & 0 deletions railties/test/application/configuration_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2139,6 +2139,76 @@ def index
end
end

test "config.autoload_lib adds lib to the autoload and eager load paths (array ignore)" do
app_file "lib/x.rb", "X = true"
app_file "lib/tasks/x.rb", "Tasks::X = true"
app_file "lib/generators/x.rb", "Generators::X = true"

add_to_config "config.autoload_lib(ignore: %w(tasks generators))"

app "development"

Rails.application.config.tap do |config|
assert_includes config.autoload_paths, "#{app_path}/lib"
assert_includes config.eager_load_paths, "#{app_path}/lib"
end

assert X
assert_raises(NameError) { Tasks }
assert_raises(NameError) { Generators }
end

test "config.autoload_lib adds lib to the autoload and eager load paths (empty array ignore)" do
app_file "lib/x.rb", "X = true"
app_file "lib/tasks/x.rb", "Tasks::X = true"

add_to_config "config.autoload_lib(ignore: [])"

app "development"

Rails.application.config.tap do |config|
assert_includes config.autoload_paths, "#{app_path}/lib"
assert_includes config.eager_load_paths, "#{app_path}/lib"
end

assert X
assert Tasks::X
end

test "config.autoload_lib adds lib to the autoload and eager load paths (scalar ignore)" do
app_file "lib/x.rb", "X = true"
app_file "lib/tasks/x.rb", "Tasks::X = true"

add_to_config "config.autoload_lib(ignore: 'tasks')"

app "development"

Rails.application.config.tap do |config|
assert_includes config.autoload_paths, "#{app_path}/lib"
assert_includes config.eager_load_paths, "#{app_path}/lib"
end

assert X
assert_raises(NameError) { Tasks }
end

test "config.autoload_lib adds lib to the autoload and eager load paths (nil ignore)" do
app_file "lib/x.rb", "X = true"
app_file "lib/tasks/x.rb", "Tasks::X = true"

add_to_config "config.autoload_lib(ignore: nil)"

app "development"

Rails.application.config.tap do |config|
assert_includes config.autoload_paths, "#{app_path}/lib"
assert_includes config.eager_load_paths, "#{app_path}/lib"
end

assert X
assert Tasks::X
end

test "load_database_yaml returns blank hash if configuration file is blank" do
app_file "config/database.yml", ""
app "development"
Expand Down