Skip to main content

kemal-database

Database initialization and interaction with SQLite and raw SQL in Kemal, following established project patterns.

Quellinformationen

Repository
kemalcr/kemal
Letzte Quellaktivität
29. August 2026 um 11:34
Erkannte Sprache von SKILL.md
Englisch
Sterne
3.923
Forks
206

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
kemal-database
description
Database initialization and interaction with SQLite and raw SQL in Kemal, following established project patterns.
license
MIT
# Kemal Database Integration This skill provides expert guidance on integrating SQLite databases with Kemal using the `db` and `sqlite3` shards, strictly following patterns from [`kemal-by-example`](https://github.com/sdogruyol/kemal-by-example). ## Core Mandates - **Dependencies:** Always `require "db"` and `require "sqlite3"` in the main application file. Add them to `shard.yml`. - **Connection:** Centralize the database connection in a `config/database.cr` file. Use environment variables for the URL: ```crystal module MyProject module Database extend self DATABASE_URL = ENV["DATABASE_URL"]? || "sqlite3:./db/database.db" @@mutex = Mutex.new @@connection : DB::Database? = nil def connection : DB::Database @@connection || @@mutex.synchronize { @@connection ||= DB.open(DATABASE_URL) } end end end ``` - **Schema Setup:** Use a `Schema.setup` method to run `CREATE TABLE IF NOT EXISTS` statements: ```crystal module MyProject module Schema extend self def setup Database.connection.exec <<-SQL CREATE TABLE IF NOT EXISTS items ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL ) SQL end end end ``` - **Model Mapping:** Use `include DB::Serializable` in models for automatic mapping from SQL results: ```crystal class Item include DB::Serializable property id : Int64? property name : String property created_at : String end ``` - **Data Retrieval:** Use `query_all` and `query_one?` with explicit column lists and the `as: Class` argument: - `Database.connection.query_all("SELECT id, name, created_at FROM items", as: Item)` - `Database.connection.query_one?("SELECT id, name, created_at FROM items WHERE id = ?", id, as: Item)` ## Patterns from Source Code ### Database Module Pattern (All Projects) ```crystal # config/database.cr module Blog module Database extend self DATABASE_URL = ENV["DATABASE_URL"]? || "sqlite3:./db/blog.db" @@mutex = Mutex.new @@connection : DB::Database? = nil def connection : DB::Database @@connection || @@mutex.synchronize { @@connection ||= DB.open(DATABASE_URL) } end end end ``` ### Schema Setup with Multiple Tables ```crystal # config/schema.cr module Ecommerce module Schema extend self def setup Database.connection.exec <<-SQL CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) SQL Database.connection.exec <<-SQL CREATE TABLE IF NOT EXISTS products ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, price_cents INTEGER NOT NULL, inventory_count INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ) SQL end end end ``` ### Model with DB::Serializable ```crystal # models/post.cr class Post include DB::Serializable getter id : Int64? getter title : String getter body : String getter created_at : String getter updated_at : String def initialize( @title : String, @body : String, @id : Int64? = nil, @created_at : String = Time.utc.to_s, @updated_at : String = Time.utc.to_s, ) end def self.all : Array(Post) Blog::Database.connection.query_all( "SELECT id, title, body, created_at, updated_at FROM posts ORDER BY created_at DESC", as: Post ) end def self.find(id : Int64) : Post? Blog::Database.connection.query_one?( "SELECT id, title, body, created_at, updated_at FROM posts WHERE id = ?", id, as: Post ) end def self.create(title : String, body : String) : Post now = Time.utc.to_s # `Database.connection` is a pooled `DB::Database`: a separate # `SELECT last_insert_rowid()` may run on a different connection and return # the wrong id. Read the id from the `DB::ExecResult` of the INSERT itself. result = Blog::Database.connection.exec( "INSERT INTO posts (title, body, created_at, updated_at) VALUES (?, ?, ?, ?)", title, body, now, now ) id = result.last_insert_id find(id) || raise "Failed to load post ##{id} after creation" end def update(title : String, body : String) Blog::Database.connection.exec( "UPDATE posts SET title = ?, body = ?, updated_at = ? WHERE id = ?", title, body, Time.utc.to_s, @id ) end def delete Blog::Database.connection.exec( "DELETE FROM posts WHERE id = ?", @id ) end end ``` ### Initialization in Main File ```crystal # blog.cr require "kemal" require "db" require "sqlite3" require "./config/database" require "./config/schema" require "./models/post" require "./routes/home" require "./routes/posts" Blog::Schema.setup Kemal.run ``` ## Best Practices - **Initialization:** Call `Schema.setup` before `Kemal.run` in the main application file. - **Timestamps:** Always use `Time.utc.to_s` for storing `created_at` and `updated_at` as `TEXT` in SQLite. - **ID Handling:** Use `Int64?` for IDs to accommodate auto-incrementing fields during creation. - **Transactions:** Use `Database.connection.transaction do |tx| ... end` for atomic operations. - **SQL Execution:** Use `Database.connection.exec("SQL", args)` for insert, update, and delete operations. - **Explicit Column Lists:** List all columns explicitly in SELECT statements rather than using `SELECT *`. ## When to Use - When setting up a new database or modifying an existing schema. - When implementing model methods that interact with the database. - When configuring database connections and initialization logic.
Auf GitHub ansehen