The Allure of Hanami's Repository Pattern
One of Hanami's most celebrated features is its strict separation between application logic and the database. Unlike the Active Record pattern common in other frameworks, where model objects are tightly coupled with database tables, Hanami uses the repository
pattern. This means you have 'entities'—plain Ruby objects that hold your data and business logic—and 'repositories' that are solely responsible for fetching and persisting those entities. This is a fantastic design choice. It makes your code cleaner, easier to test, and frees your domain logic from database concerns. Entities don't know how to save themselves; they are pure data structures. Repositories handle all the messy SQL, often with the help of the powerful ROM-rb (Ruby Object Mapper) library. This structure is a big reason developers are drawn to Hanami for its architectural clarity and maintainability. It promises performance by design, but also sets the stage for a classic mistake.
The Trap: Misunderstanding Data Loading
The hidden trap lies in how developers, especially those coming from different ORM backgrounds, handle associated data. The problem is a classic one in the world of database programming: the N+1 query problem. An N+1 query happens when your code fetches a list of parent items in one query (the "1") and then executes a separate query for each parent to fetch its related child items (the "N").
Imagine you have authors and books. You fetch 10 authors. Then, inside your view or serializer, you loop through each author and call a method to get their books. If you're not careful, this will result in 1 query for the authors and 10 more queries for the books—11 queries total instead of the two that are actually necessary. This issue cripples application speed, hammers the database with unnecessary requests, and scales terribly. As your user base grows, the number of queries explodes, and performance grinds to a halt.
How Hanami (Almost) Prevents It
Here's the twist: Hanami and ROM-rb are explicitly designed to prevent accidental N+1 queries. Unlike frameworks where lazy loading can easily lead to this problem, Hanami's entities are simple structs. An author entity doesn't have a `books` method you can call that will magically trigger another database query. This is a deliberate and powerful safeguard against the N+1 footgun.
The trap isn't that Hanami allows this by default; it's that developers, accustomed to the convenience of lazy-loading associations, try to replicate that behavior manually. A developer might fetch an author from the `AuthorRepository` and then, in a separate step, use the `BookRepository` inside a loop to fetch books for each author. They've bypassed the framework's protection by orchestrating the N+1 query themselves across different repository calls, falling into the very trap Hanami tries to help them avoid.
The Solution: Intentional Preloading
The correct Hanami and ROM approach is to be explicit and intentional about the data you need. Instead of fetching related data in a loop, you tell the repository to preload it all at once. This practice is often called "eager loading."
In Hanami with ROM-rb, you define associations in your relations, and then use repository methods to combine and preload the data you need in a single, efficient operation. For example, when fetching an author, you can instruct the repository to also fetch all their books in the same go. ROM provides a clean syntax, often using methods like `.combine` or `.preload`, to fetch parent and child records efficiently using a minimal number of queries.
This forces you to think about your data access patterns upfront. Instead of letting downstream code in your views trigger dozens of queries, you define your data needs at the repository level. This results in far fewer, more predictable, and dramatically faster database interactions. The framework isn't just giving you a tool; it's guiding you toward a more performant architecture.












