# 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.