SQL » Associations
Relation schemas in SQL land can be used to define canonical associations. These definitions play important role in automatic mapping of aggregates in repositories.
belongs_to (many-to-one)
The belongs_to
definition establishes a many-to-one association type.
class Post < ROM::Relation[:sql]
schema(infer: true) do
associations do
belongs_to :user
end
end
end
Naming convention
This method is a shortcut for
belongs_to :users, as: :user
has_many (one-to-many)
The has_many
definition establishes a one-to-many association type.
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_many :tasks
end
end
end
has_many-through (many-to-many)
The has_many
definition supports :through
option which establishes a
many-to-many association type.
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_many :tasks, through: :users_tasks
end
end
end
class UsersTasks < ROM::Relation[:sql]
schema(infer: true) do
associations do
belongs_to :user
belongs_to :task
end
end
end
has_one (one-to-one)
The has_one
definition establishes a one-to-one association type.
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_one :account
end
end
end
Naming convention
This method is a shortcut for
has_one :accounts, as: :account
has_one-through (one-to-one-through)
The has_one
definition supports :through
option which establishes a
one-to-one-through association type.
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_one :account, through: :users_accounts
end
end
end
class UsersAccounts < ROM::Relation[:sql]
schema(infer: true) do
associations do
belongs_to :account
belongs_to :user
end
end
end
Aliasing an association
If you want to use a different name for an association, you can use :as
option.
All association types support this feature.
For example, we have :posts
belonging to :users
but we'd like to call
them :authors
:
class Post < ROM::Relation[:sql]
schema(infer: true) do
associations do
belongs_to :user, as: :author
end
end
end
The alias is used by repositories, which means that in our example, if you load an aggregate with posts and its authors, the attribute name in post structs will be called author
Extending associations with custom views
You can use :view
option and specify which relation view should be used to extend
default association relation. Let's say you have users with many accounts through
users_accounts and you want to add attributes from the join relation to accounts:
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_many :accounts, through: :users_accounts, view: :ordered
end
end
end
class Accounts < ROM::Relation[:sql]
schema(infer: true)
view(:ordered) do
schema do
append(users_accounts[:position])
end
relation do
order(:position)
end
end
end
This way when you load users with their accounts, they will include :position
attribute from the join table and will be ordered by that attribute.
Using associations to manually preload relations
You can reuse queries that associations use in your own methods too via assoc
shortcut method:
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_many :tasks
end
end
def admin_tasks
assoc(:tasks).where(admin: true)
end
end
Setting a custom foreign-key
By default, foreign keys found in schemas are used, but you can provide custom names too via
:foreign_key
option:
class Flights < ROM::Relation[:sql]
schema(infer: true) do
associations do
belongs_to :destinations, as: :from, foreign_key: :from_id
belongs_to :destinations, as: :to, foreign_key: :to_id
end
end
end
Using a relation named differently from the table
It's a common case for legacy databases to have tables named differently from relations. Your legacy table name must be the first argument and the corresponding relation name must go with :relation
option:
class Users < ROM::Relation[:sql]
schema(infer: true) do
associations do
has_many :todos, as: :tasks, relation: :tasks
end
end
end
All association types support this option
Learn more
Check out API documentation: