| name | define-relation |
| license | MIT |
| type | atomic |
| description | Use when defining ROM Relations that map to database tables in Hanami 2.x — inherit from `Hanami::DB::Relation` with `schema :table_name, infer: true` for automatic schema introspection or explicit `schema :table_name do ... end` with typed attributes, add custom query methods as public relation methods returning filtered/reordered relations, define associations via `many_to_one`/`one_to_many` with `as:` aliases and load strategies using `combine` for eager-loading, and keep relations in sync with migrations by verifying via console. Trigger terms: ROM Relations, schema inference, associations, query methods, combine, eager-loading.
|
| metadata | {"ecosystem_sources":["rom-rb/rom","rom-rb/rom-sql","hanami/hanami-db"],"tags":["db","rom","relations","queries"],"version":"1.0.0"} |
define-relation
Use this skill when defining ROM Relations that map to database tables in Hanami 2.x.
Core principle: The Relation is the query layer. It defines how to read and filter data, not business logic.
Quick Reference
| Scenario | Approach |
|---|
| Define a Relation | Class inherits from Hanami::DB::Relation with schema :table_name, infer: true |
| Infer schema from DB | schema :table_name, infer: true |
| Define explicit schema | schema :table_name do { attribute :id, Types::Integer } end |
| Add a custom query method | Define a method that returns a restricted/ordered relation |
| Define an association | many_to_one :users, as: :user or one_to_many :posts, as: :posts |
| Access the Relation in an Action | include Deps["relations.users"] |
| Call a query method | relations.users.active or relations.users.by_email("a@b.com") |
| Verify inferred schema | bundle exec hanami console → app['relations.users'].schema |
Core Rules
-
Create the Relation file in the app or slice:
module MyApp
module Relations
class Users < Hanami::DB::Relation
schema :users, infer: true
end
end
end
-
Use infer: true for new tables where the database schema is the source of truth. ROM will introspect the table at boot.
-
Define explicit schema when you need custom type coercion or virtual attributes:
schema :users do
attribute :id, Types::Integer
attribute :email, Types::String
attribute :created_at, Types::Time
end
-
Add query methods for reusable filters. Encapsulate all query filters inside public relation methods — keep business logic, validations, and side effects out of Relations:
def active
where(status: "active")
end
def by_email(email)
where(email: email)
end
-
Define associations:
Use the association DSL macros for basic and advanced linkage between tables:
associations do
many_to_one :users, as: :author
one_to_many :posts, as: :posts
many_to_one :users, foreign_key: :author_id, as: :author
one_to_many :comments, through: :posts
end
Loading strategies: use combine to eagerly load associations in a single composed query, or load to issue a separate query:
users.combine(:posts)
users.combine(posts: :comments)
-
Keep Relations in sync with migrations:
If not using infer: true, manually sync explicit schema declarations when migrations alter the database. Verify using the console:
bundle exec hanami console
If schema mismatch or boot failure: check migration status → run pending migrations (bundle exec hanami db migrate) → restart the console → re-verify the schema. If infer: true fails at boot, confirm the table exists in the database and that the connection configuration is correct.
Common Mistakes
| Mistake | Resolution |
|---|
| Redundant declarations | Do not declare explicit attributes for columns that are already auto-inferred (causes boot errors). |
| Direct View access | Bypassing the Repository layer to query relations directly in views is an anti-pattern. |
| Singular class names | Relations map to plural database tables and must be plural: Users, not User. |
For detailed relation pattern examples, see RELATIONS.md.
Integration
| Related Skill | When to chain |
|---|
| write-migration | After any migration that changes the schema — verify Relation still matches |
| create-repository | When you need domain-level read/write operations that wrap Relations |
| define-entity | When defining the data structures returned by Relations |
| write-rom-spec (testing) | When writing tests for Relation query methods |
| build-crud-resource | Full end-to-end: Relation → Repository → Action → View → Test |