V2.0: Autoloading
Hanami uses the Zeitwerk code loader to support autoloading.
This means that you do not need to require the classes and modules you write before you use them. Instead, your classes and modules are automatically available across your application.
For example, the Bookshelf::Entities::Book
class defined in the file app/entities/book.rb
can be used in another class by simply using the constant Bookshelf::Entities::Book
.
# app/entities/book.rb
module Bookshelf
module Entities
class Book
attr_reader :title, :author
def initialize(title:, author:)
@title = title
@author = author
end
end
end
end
While this is convenient, it does mean you must adhere to Zeitwerk’s expectations around file structure, in which file paths match constant paths.
If class Book
was changed to class Novel
in the above file, the following error would be raised:
Zeitwerk::NameError: expected file bookshelf/app/entities/book.rb to define constant Bookshelf::Entities::Book, but didn’t
Moving the file from app/entities/book.rb
to app/entities/novel.rb
would address this error.
Autoloading in the app directory
The app/
directory is where you’ll put the majority of your application’s code.
When namespacing classes and modules in your app/
directory, use a top-level module namespace named after your app.
Assuming an app created via hanami new bookshelf
(which would have a top-level module Bookshelf
), this means abiding by the following structure:
Filename | Expected class or module |
---|---|
app/entities/book.rb | Bookshelf::Entities::Book |
app/entities/author.rb | Bookshelf::Entities::Author |
app/actions/books/create.rb | Bookshelf::Actions::Books::Create |
app/books/operations/create.rb | Bookshelf::Books::Operations::Create |
app/book_binder.rb | Bookshelf::BookBinder |
None of the above classes or modules need a require statement before use.
It’s worth noting that, thanks to Hanami’s component managment system, the components you write in app/
don’t commonly need to reference their collaborators using Ruby constants - they instead use the Deps mixin to access their dependencies.
If you are adding a class to the app/
directory that you want to use an autoloaded Ruby constant to reference, it’s very likely that you do not want that class to be registered in your app container. To opt out of registration, use the magic comment # auto_register: false
or one of the alternative methods discussed in “Opting out of the container” in the container and components guide.
# auto_register: false
require "dry-struct"
module Bookshelf
module Structs
class Book < Dry::Struct
attribute :title, Types::String
attribute :author, Types::String
end
end
end
Autoloading in the lib directory
Code placed in lib/bookshelf
(i.e. lib/<app_name>
) does not need to be required.
This SlackNotifier
class from lib/bookshelf
for instance can be used in app components without a require statement:
# lib/bookshelf/slack_notifier.rb
module Bookshelf
class SlackNotifier
def self.notify(message)
# ...
end
end
end
However, code placed in other directories within lib/
does need a require statement. Using code from these directories is akin to using a Ruby gem, and so a require statement is necessary.
The custom redis client below, for example, needs to be required (via require "custom_redis/client"
) when being used:
# lib/custom_redis/client.rb
module CustomRedis
class Client
end
end
# config/providers/redis.rb
Hanami.app.register_provider :redis do
start do
require "custom_redis/client"
redis = CustomRedis::Client.new(url: target["settings"].redis_url)
register "redis", redis
end
end
Constant location | Usage |
---|---|
lib/bookshelf/slack_notifier.rb | Bookshelf::SlackNotifier |
lib/custom_redis/client.rb | require “custom_redis/client” CustomRedis::Client |
Requiring gems
Autoloading does not apply to any Ruby gems you include in your project via its Gemfile. Like in any regular Ruby project, require external gems before using their constants in each file.
# Gemfile
gem "kramdown", "~> 2.4"
require "kramdown"
module Bookshelf
class Markdown
def to_html(markdown)
Kramdown::Document.new(markdown).to_html
end
end
end
Inflections
If you need to configure acronyms like “DB” or “WNBA”, add them to the inflector configuration in the app class:
# config/app.rb
require "hanami"
module Bookshelf
class App < Hanami::App
config.inflections do |inflections|
inflections.acronym "DB", "WNBA"
end
end
end
Autoloading will now expect constants like Bookshelf::DB::Connection
instead of Bookshelf::Db::Connection
.