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

Zeitwerk integration #35235

Merged
merged 2 commits into from Feb 12, 2019
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+385 −66
Diff settings

Always

Just for now

Copy path View file
@@ -44,7 +44,9 @@ gem "libxml-ruby", platforms: :ruby
gem "connection_pool", require: false

# for railties app_generator_test
gem "bootsnap", ">= 1.1.0", require: false
gem "bootsnap", ">= 1.4.0", require: false

gem "zeitwerk", ">= 1.0.0" if RUBY_ENGINE == "ruby"

This comment has been minimized.

@matthewd

matthewd Feb 13, 2019

Member

Need platform: here; if and Gemfiles don't mix well

This comment has been minimized.

@fxn

fxn Feb 13, 2019

Author Member

Oh yes.


# Active Job
group :job do
Copy path View file
@@ -166,9 +166,9 @@ GEM
childprocess
faraday
selenium-webdriver
bootsnap (1.3.2)
bootsnap (1.4.0)
msgpack (~> 1.0)
bootsnap (1.3.2-java)
bootsnap (1.4.0-java)
msgpack (~> 1.0)
builder (3.2.3)
bunny (2.13.0)
@@ -324,10 +324,10 @@ GEM
minitest-server (1.0.5)
minitest (~> 5.0)
mono_logger (1.1.0)
msgpack (1.2.4)
msgpack (1.2.4-java)
msgpack (1.2.4-x64-mingw32)
msgpack (1.2.4-x86-mingw32)
msgpack (1.2.6)
msgpack (1.2.6-java)
msgpack (1.2.6-x64-mingw32)
msgpack (1.2.6-x86-mingw32)
multi_json (1.13.1)
multipart-post (2.0.0)
mustache (1.1.0)
@@ -516,6 +516,7 @@ GEM
websocket-extensions (0.1.3)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (1.0.0)

PLATFORMS
java
@@ -535,7 +536,7 @@ DEPENDENCIES
benchmark-ips
blade
blade-sauce_labs_plugin
bootsnap (>= 1.1.0)
bootsnap (>= 1.4.0)
byebug
capybara (>= 2.15)
chromedriver-helper
@@ -588,6 +589,7 @@ DEPENDENCIES
webmock
webpacker (>= 4.0.0.rc.3)
websocket-client-simple!
zeitwerk (>= 1.0.0)

BUNDLED WITH
1.17.2
1.17.3
@@ -59,6 +59,14 @@ class Railtie < Rails::Railtie # :nodoc:
end
end

initializer "action_mailer.set_autoload_paths" do |app|
options = app.config.action_mailer

if options.show_previews && options.preview_path
ActiveSupport::Dependencies.autoload_paths << options.preview_path
end
end

This comment has been minimized.

@tenderlove

tenderlove Feb 12, 2019

Member

I really like that setting autoload paths is split from the other initializer. 👍


initializer "action_mailer.compile_config_methods" do
ActiveSupport.on_load(:action_mailer) do
config.compile_methods! if config.respond_to?(:compile_methods!)
@@ -76,12 +84,8 @@ class Railtie < Rails::Railtie # :nodoc:

if options.show_previews
app.routes.prepend do
get "/rails/mailers" => "rails/mailers#index", internal: true
get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
end

if options.preview_path
ActiveSupport::Dependencies.autoload_paths << options.preview_path
get "/rails/mailers" => "rails/mailers#index", internal: true
get "/rails/mailers/*path" => "rails/mailers#preview", internal: true
end
end
end
@@ -0,0 +1,73 @@
# frozen_string_literal: true

module ActiveSupport
module Dependencies
module ZeitwerkIntegration # :nodoc: all
module Decorations
def clear
Dependencies.unload_interlock do
Rails.autoloader.reload
end
end

def constantize(cpath)
Inflector.constantize(cpath)
end

def safe_constantize(cpath)
Inflector.safe_constantize(cpath)
end

def autoloaded_constants
Rails.autoloaders.flat_map do |autoloader|
autoloader.loaded.to_a
end
end

def autoloaded?(object)
cpath = object.is_a?(Module) ? object.name : object.to_s
Rails.autoloaders.any? { |autoloader| autoloader.loaded?(cpath) }
end
end

class << self
def take_over
setup_autoloaders
freeze_autoload_paths
decorate_dependencies
end

private

def setup_autoloaders
Dependencies.autoload_paths.each do |autoload_path|
if File.directory?(autoload_path)
if autoload_once?(autoload_path)
Rails.once_autoloader.push_dir(autoload_path)
else
Rails.autoloader.push_dir(autoload_path)
end
end
end

Rails.autoloaders.each(&:setup)
end

def autoload_once?(autoload_path)
Dependencies.autoload_once_paths.include?(autoload_path) ||
Gem.path.any? { |gem_path| autoload_path.to_s.start_with?(gem_path) }
end

def freeze_autoload_paths
Dependencies.autoload_paths.freeze
Dependencies.autoload_once_paths.freeze
end

def decorate_dependencies
Dependencies.singleton_class.prepend(Decorations)
Object.class_eval { alias_method :require_dependency, :require }
end
end
end
end
end
Copy path View file
@@ -110,5 +110,25 @@ def groups(*groups)
def public_path
application && Pathname.new(application.paths["public"].first)
end

def autoloader
if configuration.autoloader == :zeitwerk
@autoloader ||= Zeitwerk::Loader.new
end
end

def once_autoloader
if configuration.autoloader == :zeitwerk
@once_autoloader ||= Zeitwerk::Loader.new
end
end

def autoloaders
if configuration.autoloader == :zeitwerk
[autoloader, once_autoloader]
else
[]
end
end
end
end
@@ -20,7 +20,7 @@ class Configuration < ::Rails::Engine::Configuration
:read_encrypted_secrets, :log_level, :content_security_policy_report_only,
:content_security_policy_nonce_generator, :require_master_key, :credentials

attr_reader :encoding, :api_only, :loaded_config_version
attr_reader :encoding, :api_only, :loaded_config_version, :autoloader

def initialize(*)
super
@@ -64,6 +64,7 @@ def initialize(*)
@credentials = ActiveSupport::OrderedOptions.new
@credentials.content_path = default_credentials_content_path
@credentials.key_path = default_credentials_key_path
@autoloader = :classic
end

def load_defaults(target_version)
@@ -117,6 +118,8 @@ def load_defaults(target_version)
when "6.0"
load_defaults "5.2"

self.autoloader = :zeitwerk if RUBY_ENGINE == "ruby"

if respond_to?(:action_view)
action_view.default_enforce_utf8 = false
end
@@ -267,6 +270,14 @@ def content_security_policy(&block)
end
end

def autoloader=(autoloader)
if %i(classic zeitwerk).include?(autoloader)
@autoloader = autoloader
else
raise ArgumentError, "config.autoloader may be :classic or :zeitwerk, got #{autoloader.inspect} instead"
end
end

class Custom #:nodoc:
def initialize
@configurations = Hash.new
@@ -21,6 +21,13 @@ module Finisher
end
end

initializer :let_zeitwerk_take_over do
if config.autoloader == :zeitwerk
require "active_support/dependencies/zeitwerk_integration"
ActiveSupport::Dependencies::ZeitwerkIntegration.take_over
end
end

initializer :add_builtin_route do |app|
if Rails.env.development?
app.routes.prepend do
@@ -66,6 +73,7 @@ module Finisher
initializer :eager_load! do
if config.eager_load
ActiveSupport.run_load_hooks(:before_eager_load, self)
Zeitwerk::Loader.eager_load_all if defined?(Zeitwerk)
config.eager_load_namespaces.each(&:eager_load!)
end
end
Copy path View file
@@ -472,12 +472,10 @@ def load_generators(app = self)
# Eager load the application by loading all ruby
# files inside eager_load paths.
def eager_load!
config.eager_load_paths.each do |load_path|
# Starts after load_path plus a slash, ends before ".rb".
relname_range = (load_path.to_s.length + 1)...-3
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file[relname_range]
end
if Rails.autoloader
eager_load_with_zeitwerk!
else
eager_load_with_dependencies!
end
end

@@ -653,6 +651,22 @@ def run_tasks_blocks(*) #:nodoc:

private

def eager_load_with_zeitwerk!
(config.eager_load_paths - Zeitwerk::Loader.all_dirs).each do |path|
Dir.glob("#{path}/**/*.rb").sort.each { |file| require file }
end
end

def eager_load_with_dependencies!
config.eager_load_paths.each do |load_path|
# Starts after load_path plus a slash, ends before ".rb".
relname_range = (load_path.to_s.length + 1)...-3
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file[relname_range]
end
end
end

def load_config_initializer(initializer) # :doc:
ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do
load(initializer)
@@ -28,15 +28,17 @@ ruby <%= "'#{RUBY_VERSION}'" -%>

<% if depend_on_bootsnap? -%>
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.1.0', require: false
gem 'bootsnap', '>= 1.4.0', require: false

<%- end -%>
<%- if options.api? -%>
# Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
# gem 'rack-cors'

<%- end -%>
<% if RUBY_ENGINE == 'ruby' -%>
<% if RUBY_ENGINE == "ruby" -%>
gem "zeitwerk", ">= 1.0.0"

This comment has been minimized.

@matthewd

matthewd Feb 13, 2019

Member

All the rest of this file uses single quotes

This comment has been minimized.

@fxn

fxn Feb 13, 2019

Author Member

But our style guidelines have changed over time, saw an opportunity to update that one I was touching.

True that the file should be consistent.


group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
@@ -1157,6 +1157,27 @@ def index
end
end
test "autoloader & autoloader=" do
app "development"
config = Rails.application.config
assert_instance_of Zeitwerk::Loader, Rails.autoloader
assert_instance_of Zeitwerk::Loader, Rails.once_autoloader
assert_equal [Rails.autoloader, Rails.once_autoloader], Rails.autoloaders
config.autoloader = :classic
assert_nil Rails.autoloader
assert_nil Rails.once_autoloader
assert_empty Rails.autoloaders
config.autoloader = :zeitwerk
assert_instance_of Zeitwerk::Loader, Rails.autoloader
assert_instance_of Zeitwerk::Loader, Rails.once_autoloader
assert_equal [Rails.autoloader, Rails.once_autoloader], Rails.autoloaders
assert_raises(ArgumentError) { config.autoloader = :unknown }
end
test "config.action_view.cache_template_loading with cache_classes default" do
add_to_config "config.cache_classes = true"
@@ -85,6 +85,7 @@ def foo
end

test "mailer previews are loaded from a custom preview_path" do
app_dir "lib/mailer_previews"
add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'"

mailer "notifier", <<-RUBY
@@ -254,6 +255,7 @@ def foo
end

test "mailer previews are reloaded from a custom preview_path" do
app_dir "lib/mailer_previews"
add_to_config "config.action_mailer.preview_path = '#{app_path}/lib/mailer_previews'"

app("development")
@@ -818,6 +820,7 @@ def foo
def build_app
super
app_file "config/routes.rb", "Rails.application.routes.draw do; end"
app_dir "test/mailers/previews"
end
def mailer(name, contents)
@@ -100,30 +100,6 @@ def test_multiple_applications_can_be_initialized
assert_nothing_raised { AppTemplate::Application.new }
end

def test_initializers_run_on_different_applications_go_to_the_same_class
application1 = AppTemplate::Application.new
run_count = 0

AppTemplate::Application.initializer :init0 do
run_count += 1
end

application1.initializer :init1 do
run_count += 1
end

AppTemplate::Application.new.initializer :init2 do
run_count += 1
end

assert_equal 0, run_count, "Without loading the initializers, the count should be 0"

# Set config.eager_load to false so that an eager_load warning doesn't pop up
AppTemplate::Application.create { config.eager_load = false }.initialize!

assert_equal 3, run_count, "There should have been three initializers that incremented the count"
end

def test_consoles_run_on_different_applications_go_to_the_same_class
run_count = 0
AppTemplate::Application.console { run_count += 1 }
Oops, something went wrong.
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.