# Zeitwerk ## Intro ```ruby class PostsController < ApplicationController def index @posts = Post.all end end ``` - Rails application do `require` from Ruby standard library, Ruby gems, Rails's $LOAD_PATH and lib/. - Rails 5 using Active Support Autoload, it called :classic now. - In Rails 6.0 - Add :zeitwerk mode - :classic can still use - Rails 7 removed :classic. ## Nesting & Namespace ```ruby module XML class SAXParser # (1) end end ``` =>[XML::SAXParser, XML] ```ruby class XML::SAXParser # (2) end ``` =>[XML::SAXParser] ## $LOAD_PATH ``` rails runner 'puts ActiveSupport::Dependencies.autoload_paths' => app/models/fields app/models/fields/concerns app/models/fields_data app/models/domain_data app/models/domain_data/concerns app/browserslist app/carriers app/controllers app/controllers/concerns app/decorators app/extras app/graphql app/graphql/concerns app/helpers app/inputs app/mailers app/mailers/concerns app/models app/models/concerns app/presenters app/queries app/sanitizers app/serializers app/services app/uploaders app/uploaders/concerns app/validators app/workers ``` ## Example ```ruby module Admin class PostsController < ApplicationController def index @posts = Post.all end end end ``` ```ruby Admin::PostsController::Post Admin::Post Post # admin/posts_controller/post.rb # admin/post.rb # post.rb ``` ```ruby app/assets/posts_controller/post.rb app/controllers/posts_controller/post.rb app/helpers/posts_controller/post.rb ... test/mailers/previews/posts_controller/post.rb ``` ```ruby app/assets/post.rb app/controllers/post.rb app/helpers/post.rb app/mailers/post.rb app/models/post.rb ``` =>Post ## Autoload before Rails 6 - Autoloading in Rails was based on const_missing up to Rails 5. - That callback lacks fundamental information. - No nesting or the resolution algorithm being used. - Autoloading was not able to match Ruby's semantics, and that introduced a series of issues. ## require - There is no static way to verify that you have issued the require calls for code that your file depends on. - `require` has global side-effects =>That introduces bugs that depend on the load order. - Namespaces - require "foo/bar" may define Foo, instead of reopen it. It give place to superclass mismatches or partially-defined namespaces. ## Zeitwerk - Independent gem - Design for any Ruby project - Support coexisting loaders - Integrate in Rails ### Basic - Zeitwerk is based on a different technique and fixed Rails autoloading starting with Rails 6. - In Rails applications, `filenames` must match the `constants` they define, with the directory acting as the `namespace`. - No need require and require_dependency - Thread-safety Everywhere - Eager Loading and Autoloading are Consistent - Support gem development - Custom and Callback For example: Zeitwerk use String#camelize It expect app/controllers/users_controller.rb to define `UsersController` `"users_controller".camelize # => UsersController` app/controllers/admin/payments_controller.rb should define `Admin::PaymentsController` ```ruby # config/application.rb config.autoload_paths += %w( app/models/fields app/models/fields/concerns app/models/fields_data app/models/domain_data app/models/domain_data/concerns ) ``` ### Inflector & Custom inflector - 縮寫 - 特殊目的或意義 - `require 'app/servers/api_server'` - In the case of api_server, the API is considered an abbreviation so it should not be written as a file name like a_p_i_server. - ActiveSupport.inflector ```ruby class MyInflector < Zeitwerk::Inflector def camelize(basename, _abspath) case basename when 'api_server' then 'APIServer' when 'html_parser' then 'HTMLParser' else super end end end # config/initializers/zeitwerk.rb Rails.autoloaders.each do |autoloader| autoloader.inflector = Zeitwerk::Inflector.new autoloader.inflector.inflect( "api_server" => "APIServer", "html_parser" => "HTMLParser", ) end ``` ### classic mode vs zeitwerk mode - classic mode underscores constant names ("User" -> "user.rb") - zeitwerk mode camelizes file names ("user.rb" -> "User"). - You just need to name things by convention in zeitwerk mode. ### Tips - API to remove an autoload ```ruby class Hotel # Go to directory and confirm hotel namespace, # set autoloder and contiunue. include Pricing end module Hotel::Pricing end ``` ### Reloading Zeitwerk is able to reload code, but you need to enable this feature: ``` loader = Zeitwerk::Loader.new loader.push_dir(...) loader.enable_reloading # you need to opt-in before setup loader.setup ... loader.reload ``` ### Callbacks The on_setup callback ```ruby # some_api_client.rb class SomeApiClient class << self attr_accessor :endpoint end end # config/environments/development.rb loader.on_load("SomeApiClient") do |klass, _abspath| klass.endpoint = "https://api.dev" end # config/environments/production.rb loader.on_load("SomeApiClient") do |klass, _abspath| klass.endpoint = "https://api.prod" end ``` - Delaying the execution of the block until the class is loaded for performance. - Delaying the execution of the block until the class is loaded because it follows the `adapter pattern` and better not to load the class if the user does not need it.