luna
Version, currently master branch1 version
- master branchlatestAug 19, 2026
github.com/HarbinsonAdam/luna.cr
A rails like model framework built off of ActiveModel to work with SQL and NoSQL Databases
Installation
# Add this to your shard.yml
dependencies:
luna:
github: HarbinsonAdam/luna.cr
branch: mastermaster is a branch, not a release, so this tracks it rather than pinning a version.
Then run:
shards installshard.yml
- Crystal
>= 1.10.1- License
- MIT
- Author
- Adam Harbinson
Dependencies
Runtime Dependencies
- db*github: crystal-lang/crystal-db
- active-model*github: spider-gazelle/active-model
Development Dependencies
README
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
sqlite3:
github: crystal-lang/crystal-sqlite3
Then:
shards install
Setup
Register at least one connection before using models:
require "luna/adapters/sqlite3"
require "luna"
Luna::Setup.register :default, "sqlite3:./db/app.db"
# Optional additional connections
Luna::Setup.register :reports, "sqlite3:./db/reports.db"
# Optional: Rails-style SQL logging with elapsed ms
Luna::Setup.enable_query_logging
Supported URL schemes include sqlite3:, postgres://, and mysql://.
Luna no longer hard-depends on every database adapter. Add only the adapter shard you need and require the matching adapter loader before require "luna":
require "luna/adapters/sqlite3"
require "luna"
require "luna/adapters/pg"
require "luna"
require "luna/adapters/mysql"
require "luna"
When query logging is enabled, logs include model/action labels, elapsed time, SQL, params, and transaction markers:
Product Load (0.3ms) SELECT * FROM products WHERE id = ? LIMIT ? -- params: [81, 1]
(0.0ms) begin transaction
Product Update (0.2ms) UPDATE products SET name = ? WHERE id = ? -- params: ["Desk", 81]
(0.4ms) commit transaction
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
State Machines
Luna supports enum-backed state machines with Rails-style transition events.
enum JobState
ENQUEUED
RUNNING
FAILED
COMPLETED
end
class RenderJob < Luna::BaseModel
primary_key id
attribute status : JobState, default: JobState::ENQUEUED
state_machine status, JobState, {
start: {from: :enqueued, to: :running},
fail: {from: [:enqueued, :running], to: :failed},
finish: {from: :running, to: :completed, after: :emit_completed_event},
}
end
What this gives you:
- Enum values persist to the DB as lowercase strings (for example
completed) - Event methods (
start,start!, etc.) with transition guards - Exceptions on invalid transitions (
Luna::InvalidStateTransition) - Save-time protection against invalid direct state assignment (
model.status = ...; model.save) - Per-state predicate helpers (for example
status_running?)
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
Documentation
Built from the current release. The first visit to a release nobody has asked for starts its build.
Links
This branch
- Branch
master- Seen
- Aug 19, 2026
- Crystal
>= 1.10.1- Indexed
- yes
Dependents
No indexed shard depends on this one yet.
Repository
github.com/HarbinsonAdam/luna.cr
Metadata
- Created
- Aug 12, 2026
- Updated
- Sep 17, 2026
- Synced
- Sep 17, 2026
- Versions
- 1