onyx-sql
Core is an expressive modular ORM for Crystal featuring:
- ⚡️ Efficiency based on Crystal performance
- ✨ Expressiveness with powerful DSL and lesser code
- 💼 Safety with strictly typed attributes
About
Core does not follow Active Record pattern, it's more like a data-mapping solution. There is a concept of Repository, which is basically a gateway to the database. For example:
repo = Core::Repository.new(db)
users = repo.query(User, "SELECT * FROM users WHERE id > 42")
users.class # => Array(User)
Core also has a plently of features, including:
- Expressive Query builder, either standalone or module, allowing to use constructions like
Post.join(:author).where(author_id: 42)
, which turns into a plain SQL - Validations module allowing to perform both inline and custom validations (
user.valid? # => true
)
However, Core is designed to be minimal, so it doesn't perform task you may got used to, for example, it doesn't do database migrations itself. You may use migrate instead.
Installation
Add this to your application's shard.yml
:
dependencies:
core:
github: vladfaust/core.cr
version: ~> 0.4.0 # See actual version in releases
This shard follows Semantic Versioning v2.0.0, so check releases and change the version
accordingly.
Basic example
Assuming following database schema:
CREATE TABLE users(
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE posts(
id SERIAL PRIMARY KEY,
author_id INT NOT NULL REFERENCES users (id),
content TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ
);
require "core"
require "pg" # Or maybe another driver
class User
include Core::Schema
include Core::Query
include Core::Validations
schema :users do
primary_key :id
reference :posts, Array(Post), foreign_key: :author_id
field :name, String, validate: {size: (3..100)}
field :created_at, Time, db_default: true # Means that DB is handling the default value
end
end
class Post
include Core::Schema
include Core::Query
include Core::Validations
schema :posts do
primary_key :id
reference :author, User, key: :author_id
field :content, String
field :created_at, Time, db_default: true
field :updated_at, Time?
end
end
db = DB.open(ENV["DATABASE_URL"])
query_logger = Core::Logger::IO.new(STDOUT)
repo = Core::Repository.new(db, query_logger)
user = User.new(name: "Vl")
user.valid? # => false
user.errors # => [{:name => "must have size in range of 3..100"}]
user.name = "Vlad"
user = repo.insert(user)
post = repo.insert(Post.new(author: user, content: "What a beauteful day!")) # Oops
post.content = "What a beautiful day!"
repo.update(post)
posts = repo.query(Post.where(author: user).join(:author))
puts posts.first.inspect
# => <Post @author=<User @name="Vlad"> @content="What a beautiful day!">
Testing
- Apply migration from
./spec/migration.sql
- Run
env DATABASE_URL=your_database_url crystal spec
Contributing
- Fork it ( https://github.com/vladfaust/core.cr/fork )
- Create your feature branch (git checkout -b my-new-feature)
- Commit your changes (git commit -am 'Add some feature')
- Push to the branch (git push origin my-new-feature)
- Create a new Pull Request
Contributors
- @vladfaust Vlad Faust - creator, maintainer