ESC
Type to search...
S
Soli Docs

Relationships

Define associations between models using a simple DSL. SoliLang handles foreign keys, eager loading, and join filtering automatically.

Relationship DSL

Declare associations inside your model class using the built-in relationship methods:

Method Description
has_many(name) Declare a one-to-many relationship
has_one(name) Declare a one-to-one relationship
belongs_to(name) Declare an inverse relationship
belongs_to(name, { "polymorphic": true }) Declare a polymorphic relationship

Defining Relationships

Add relationship declarations at the top of your model class body:

class User < Model
    has_many("posts")
    has_one("profile")
end

class Post < Model
    belongs_to("user")
    has_many("comments")
end

Naming Conventions

SoliLang automatically infers the related class, collection, and foreign key from the relationship name:

Declaration Related Class Collection Foreign Key
has_many("posts") Post posts user_id
has_one("profile") Profile profiles user_id
belongs_to("user") User users user_id

Custom Foreign Keys

Override the default naming conventions with an options hash:

class Post < Model
    belongs_to("author", { "class_name": "User", "foreign_key": "author_id" })
end

Polymorphic Relationships

A polymorphic belongs_to allows a model to belong to more than one other model on a single association. The related type and ID are stored in commentable_type and commentable_id fields:

class Comment < Model
    belongs_to("commentable", { "polymorphic": true })
end

Relationship Accessors

Access related records directly from model instances. You can also chain query builder methods on has_many relations:

let user = User.find("user_id")

# Access has_many relation
let posts = user.posts

# Access has_one relation
let profile = user.profile

# Access belongs_to relation
let author = post.user

# Chain query builder methods on relations
let published = user.posts.where("published = @p", { "p": true }).all

Eager Loading (includes)

Without eager loading, accessing relations in a loop triggers a separate query for each record — the classic N+1 problem. Use .includes() to preload related records in a single query via LET subqueries with MERGE:

# Load users with their posts and profiles in a single query
let users = User.includes("posts", "profile").all

# Combine with where clauses
let active = User.where("active = @a", { "a": true }).includes("posts").first

# Inspect the generated query
print(User.includes("posts").to_query)

has_many includes return an array. has_one and belongs_to includes return a single document (via FIRST()).

Join Filtering

Filter records by the existence of related records. Unlike includes, join does not preload the related data — it only filters the parent records:

# Find users who have at least one post
let users_with_posts = User.join("posts").all

# Find users who have published posts
let count = User.join("posts", "published = @p", { "p": true }).count

# Chain with other query methods
let recent = User.join("posts").order("created_at", "desc").limit(10).all

Filtered Includes

Filter included relations to load only matching related records:

# Only load published posts for each user
let users = User.includes("posts", "published = @p", { "p": true }).all

# Combine a filter with field projection using the "fields" key
let users = User.includes("posts", "published = @p", {
    "p": true,
    "fields": ["title", "body"]
}).all

Includes with Field Projection

Use a hash argument to select specific fields on included relations (without filtering):

# Only load title and body from posts
let users = User.includes({ "posts": ["title", "body"] }).all

Chaining Multiple Includes

Chain .includes() calls to eagerly load multiple relations with different options:

# Filtered posts + unfiltered profile
let users = User.includes("posts", "published = @p", { "p": true })
    .includes("profile")
    .all

Manual Relationships

For more control, implement relationships as custom instance methods:

class Post < Model
    def author
        User.find(this.author_id)
    end
end

Next Steps