luna.cr
Luna.cr
Luna is an ActiveRecord-style ORM for Crystal, built on top of active-model.
It supports:
- Model persistence (
save,update,destroy) - Relation-style querying (
where,order,limit, aggregates) - Associations (
belongs_to,has_one,has_many) - Transactions
- Migrations
- Single Table Inheritance (STI)
Installation
Add to shard.yml:
dependencies:
luna:
github: HarbinsonAdam/luna.cr
Then:
shards install
Setup
Register at least one connection before using models:
require "luna"
Luna::Setup.register :default, "sqlite3:./db/app.db"
# Optional additional connections
Luna::Setup.register :reports, "sqlite3:./db/reports.db"
Supported URL schemes include sqlite3:, postgres://, and mysql://.
Defining Models
class User < Luna::BaseModel
primary_key id
attribute name : String
attribute email : String
attribute active : Bool, default: true
end
Use a non-default connection:
class AuditLog < Luna::BaseModel
connection :reports
primary_key id
attribute message : String
end
CRUD
# Create
user = User.new(name: "Ada", email: "ada@example.com")
user.save
# Read
found = User.find(user.id.not_nil!) # User?
found! = User.find!(user.id.not_nil!) # raises Luna::RecordNotFound if missing
# Update
found!.active = false
found!.save
# Delete
found!.destroy
Querying (Relation API)
all and where return a relation, so you can chain:
relation = User.where({active: true}).order("id DESC").limit(10)
users = relation.all
first_user = relation.first
Other helpers:
User.count
User.exists?
User.exists?({email: "ada@example.com"})
User.pluck("email", as: String)
User.sum("id", as: Int64)
User.avg("id", as: Float64)
User.min("id", as: Int64)
User.max("id", as: Int64)
Associations
class Author < Luna::BaseModel
primary_key id
attribute name : String
has_many posts, Post
end
class Post < Luna::BaseModel
primary_key id
attribute author_id : Int64?
attribute title : String
belongs_to author, Author
end
Eager loading:
posts = Post.order("id ASC").includes(:author).all
Nested eager loading is supported:
authors = Author.includes(posts: :comments).all
Transactions
Global transaction:
Luna.transaction do
User.new(name: "A", email: "a@example.com").save
User.new(name: "B", email: "b@example.com").save
end
Model-scoped transaction (uses model connection):
User.transaction do
# ...
end
Rollback without bubbling an error:
Luna.transaction do
User.new(name: "Temp", email: "temp@example.com").save
raise Luna::Rollback.new
end
STI (Single Table Inheritance)
Parent model defines discriminator column with sti. Child models define type with sti_type.
class Animal < Luna::BaseModel
primary_key id
sti kind
attribute name : String
end
class Dog < Animal
sti_type :dog
attribute bark_volume : Int64?
end
class Cat < Animal
sti_type :cat
attribute lives_left : Int64?
end
Behavior:
Dog/Catuse the parent table- Saving a child sets
kindautomatically - Querying
Animalhydrates the correct subclass - Querying
DogorCatautomatically scopes by type
Migrations
Define a migration by inheriting Luna::BaseMigration. Migrations auto-register when inherited.
class V20260101000000_CreateUsers < Luna::BaseMigration
def change
create_table :users, id: true do |t|
t.string :name, null: false
t.string :email, null: false
t.boolean :active, default: true
t.timestamps
end
add_index :users, :email, unique: true
end
end
Run migrations:
runner = Luna::MigrationRunner.new(:default)
runner.run_migrations
Development
Run specs:
crystal spec
Luna.cr is currently in development. The project is mirrored from a private GitLab repository to GitHub for public access.
Contributors
- Adam Harbinson - creator and maintainer
luna.cr
- 0
- 0
- 0
- 0
- 4
- about 1 hour ago
- March 2, 2026
MIT License
Mon, 02 Mar 2026 23:24:17 GMT