Associations¶
module
Defined in src/ralph/associations.cr:94
Associations module for defining model relationships
This module provides macros for defining common database associations:
- belongs_to - Many-to-one relationship (e.g., a post belongs to a user)
- has_one - One-to-one relationship (e.g., a user has one profile)
- has_many - One-to-many relationship (e.g., a user has many posts)
Polymorphic associations are also supported:
- belongs_to commentable : Model, polymorphic: true - Can belong to multiple model types
- has_many comments : Comment, as: :commentable - Parent side of polymorphic relationship
New in Phase 3.3:
- counter_cache: true - Maintain a count column on parent for has_many associations
- touch: true - Update parent timestamp when association changes
- Association scoping with lambda blocks
- Through associations: has_many tags : Tag, through: :posts
Example:
class Post < Ralph::Model
column id : Int64, primary: true
column title : String
column user_id : Int64
belongs_to user : User, touch: true
end
class User < Ralph::Model
column id : Int64, primary: true
column name : String
column posts_count : Int32, default: 0
column updated_at : Time?
has_one profile : Profile
has_many posts : Post, counter_cache: true
has_many tags : Tag, through: :posts
end
Class Methods¶
.counter_cache_registry¶
Get the counter cache registry
.counter_caches_for(child_class : String) : Array(NamedTuple(parent_class: String, association_name: String, counter_column: String, foreign_key: String)) | Nil¶
Get counter caches for a child class
.find_polymorphic(class_name : String, id_str : String) : Ralph::Model | Nil¶
Lookup and find a polymorphic record by class name and id (as string) The id is passed as a string to support flexible primary key types
.polymorphic_registry¶
Get the polymorphic registry
.register_counter_cache(child_class : String, parent_class : String, association_name : String, counter_column : String, foreign_key : String)¶
Register a counter cache relationship
.register_polymorphic_type(class_name : String, finder : Proc(String, Ralph::Model | Nil))¶
Register a model class for polymorphic lookup
This is called at runtime when models with as: option are loaded
Uses String for flexible primary key type support (Int64, UUID, String, etc.)
.register_touch(child_class : String, parent_class : String, association_name : String, touch_column : String, foreign_key : String)¶
Register a touch relationship
.touch_registry¶
Get the touch registry
.touches_for(child_class : String) : Array(NamedTuple(parent_class: String, association_name: String, touch_column: String, foreign_key: String)) | Nil¶
Get touch relationships for a child class
Macros¶
.belongs_to(klass_or_decl = nil, **options)¶
Define a belongs_to association
Options:
- foreign_key: Specify a custom foreign key column (e.g., "author_id" instead of "user_id")
- primary_key: Specify the primary key on the associated model (defaults to "id")
- touch: If true, updates parent's updated_at on save; can also be a column name
- counter_cache: If true, maintains a count column on the parent model
- true: Uses default column name (e.g., posts_count for belongs_to Post)
- String: Uses custom column name (e.g., counter_cache: "comment_count")
- optional: If true, the foreign key can be nil (default: false)
For polymorphic associations, use the special form: belongs_to polymorphic: :commentable
Supports two syntaxes: belongs_to User # Association name inferred as 'user' belongs_to author : User # Explicit association name
Usage:
belongs_to User # user, user_id
belongs_to author : User # author, author_id (explicit name)
belongs_to author : User, foreign_key: :writer_id # author, writer_id
belongs_to User, primary_key: :uuid # user, user_id (looks up by uuid)
belongs_to polymorphic: :commentable # commentable_id, commentable_type columns
belongs_to User, touch: true # Updates user.updated_at on save
belongs_to User, touch: :last_post_at # Updates user.last_post_at on save
belongs_to User, counter_cache: true # Maintains user.posts_count
belongs_to User, counter_cache: "total_books" # Uses custom column name
.has_many(klass_or_decl, scope_block = nil, **options)¶
Define a has_many association
Options: - foreign_key: Specify a custom foreign key on the associated model (e.g., "owner_id" instead of "user_id") - primary_key: Specify the primary key on this model (defaults to "id") - polymorphic: For polymorphic associations, specify the name of the polymorphic interface on the child - through: For through associations, specify the intermediate association name - source: For through associations, specify the source association on the through model - dependent: Specify what happens to associated records when this record is destroyed - :destroy - Destroy associated records (runs callbacks) - :delete_all - Delete associated records (skips callbacks) - :nullify - Set foreign key to NULL - :restrict_with_error - Prevent destruction if associations exist (adds error) - :restrict_with_exception - Prevent destruction if associations exist (raises exception)
Note: For counter caching, use counter_cache: true on the belongs_to side of the association.
This automatically generates increment/decrement/update callbacks on the child model.
Supports two syntaxes: has_many Post # Association name inferred as 'posts' has_many posts : Post # Explicit association name
Usage:
has_many Post # posts
has_many articles : Post # articles (explicit name)
has_many articles : BlogPost, foreign_key: :writer_id # articles, writer_id
has_many Post, dependent: :destroy # destroys posts when parent destroyed
has_many Post, dependent: :delete_all # deletes without callbacks
has_many comments : Comment, polymorphic: :commentable # polymorphic (Comment has commentable_id/type)
has_many tags : Tag, through: :post_tags # through association
has_many tags : Tag, through: :post_tags, source: :tag # through with custom source
.has_one(klass_or_decl, **options)¶
Define a has_one association
Options: - foreign_key: Specify a custom foreign key on the associated model (e.g., "owner_id" instead of "user_id") - primary_key: Specify the primary key on this model (defaults to "id") - polymorphic: For polymorphic associations, specify the name of the polymorphic interface on the child - dependent: Specify what happens to associated records when this record is destroyed - :destroy - Destroy associated records (runs callbacks) - :delete - Delete associated records (skips callbacks) - :nullify - Set foreign key to NULL - :restrict_with_error - Prevent destruction if associations exist (adds error) - :restrict_with_exception - Prevent destruction if associations exist (raises exception)
Supports two syntaxes: has_one Profile # Association name inferred as 'profile' has_one user_profile : Profile # Explicit association name
Usage:
has_one Profile # profile
has_one user_profile : Profile # user_profile (explicit name)
has_one avatar : UserAvatar, foreign_key: :owner_id # avatar, owner_id on UserAvatar
has_one Profile, dependent: :destroy # destroys profile when user destroyed
has_one profile : Profile, polymorphic: :profileable # polymorphic (Profile has profileable_id/type)