|
| 1 | +# Core |
| 2 | + |
| 3 | +**C**rystal **O**bject **RE**lational Mapping you've been waiting for. |
| 4 | + |
| 5 | +[](https://travis-ci.org/vladfaust/core.cr) [](https://vladfaust.com/core.cr) [](https://shards.rocks/github/vladfaust/core.cr) [](https://github.com/vladfaust/core.cr/releases) |
| 6 | + |
| 7 | +## About |
| 8 | + |
| 9 | +Tired of [ActiveRecord](https://wikipedia.org/wiki/Active_record_pattern)'s magic? Forget it. It's time for real programming! |
| 10 | + |
| 11 | +**Core** is inspired by [Crecto](https://github.com/Crecto/crecto) but more transparent and Crystal-ish. |
| 12 | + |
| 13 | +## Installation |
| 14 | + |
| 15 | +Add this to your application's `shard.yml`: |
| 16 | + |
| 17 | +```yaml |
| 18 | +dependencies: |
| 19 | + core: |
| 20 | + github: vladfaust/core.cr |
| 21 | + version: ~> 0.1.0 |
| 22 | +``` |
| 23 | +
|
| 24 | +## Usage |
| 25 | +
|
| 26 | +Assuming following initial database migration: |
| 27 | +
|
| 28 | +```sql |
| 29 | +CREATE TABLE users( |
| 30 | + id SERIAL PRIMARY KEY, |
| 31 | + name VARCHAR(100) NOT NULL, |
| 32 | + created_at TIMESTAMPTZ NOT NULL, |
| 33 | + updated_at TIMESTAMPTZ |
| 34 | +); |
| 35 | + |
| 36 | +CREATE TABLE posts( |
| 37 | + id SERIAL PRIMARY KEY, |
| 38 | + author_id INT NOT NULL REFERENCES users (id), |
| 39 | + content TEXT NOT NULL, |
| 40 | + created_at TIMESTAMPTZ NOT NULL, |
| 41 | + updated_at TIMESTAMPTZ |
| 42 | +); |
| 43 | +``` |
| 44 | + |
| 45 | +```crystal |
| 46 | +require "core" |
| 47 | +require "db" |
| 48 | +require "pg" # Or another driver |
| 49 | +
|
| 50 | +class User < Core::Model |
| 51 | + schema do |
| 52 | + primary_key :id |
| 53 | + field :name, String |
| 54 | + virtual_field :posts_count, Int64 |
| 55 | + reference :posts, Array(Post), foreign_key: :author_id |
| 56 | + created_at_field :created_at |
| 57 | + updated_at_field :updated_at |
| 58 | + end |
| 59 | +
|
| 60 | + validation do |
| 61 | + errors.push({:name => "length must be >= 3"}) unless name.try &.size.>= 3 |
| 62 | + end |
| 63 | +end |
| 64 | +
|
| 65 | +class Post < Core::Model |
| 66 | + schema do |
| 67 | + primary_key :id |
| 68 | + field :content, String |
| 69 | + reference :author, User, key: :author_id |
| 70 | + created_at_field :created_at |
| 71 | + updated_at_field :updated_at |
| 72 | + end |
| 73 | +end |
| 74 | +
|
| 75 | +db = DB.open(ENV["DATABASE_URL"]) |
| 76 | +query_logger = Core::QueryLogger.new(STDOUT) |
| 77 | +
|
| 78 | +user_repo = Core::Repository(User).new(db, query_logger) |
| 79 | +post_repo = Core::Repository(Post).new(db, query_logger) |
| 80 | +
|
| 81 | +user = User.new(name: "Fo") |
| 82 | +user.valid? # => false |
| 83 | +user.errors # => [{:name => "length must be >= 3"}] |
| 84 | +user.name = "Foo" |
| 85 | +user.valid? # => true |
| 86 | +
|
| 87 | +user.id = user_repo.insert(user) # See ^1 |
| 88 | +# INSERT INTO users (name, created_at) VALUES ($1, $2) RETURNING id |
| 89 | +
|
| 90 | +post = Post.new(author: user, content: "Foo Bar") |
| 91 | +post.id = post_repo.insert(post) # See ^1 |
| 92 | +# INSERT INTO posts (author_id, content, created_at) VALUES ($1, $2, $3) RETURNING id |
| 93 | +
|
| 94 | +alias Query = Core::Query |
| 95 | +
|
| 96 | +posts = post_repo.query(Query(Post).where(author: user)) |
| 97 | +# SELECT * FROM posts WHERE author_id = $1 |
| 98 | +
|
| 99 | +posts.first.content # => "Foo Bar" |
| 100 | +
|
| 101 | +query = Query(User) |
| 102 | + .join(:posts) |
| 103 | + .select(:*, :"COUNT(posts.id) AS posts_count") |
| 104 | + .group_by(%i(users.id posts.id)) |
| 105 | + .one |
| 106 | +user = user_repo.query(query).first |
| 107 | +# SELECT *, COUNT (posts.id) AS posts_count |
| 108 | +# FROM users JOIN posts AS posts ON posts.author_id = users.id |
| 109 | +# GROUP BY users.id, posts.id LIMIT 1 |
| 110 | +
|
| 111 | +user.posts_count # => 1 |
| 112 | +
|
| 113 | +user.name = "Bar" |
| 114 | +user.changes # => {:name => "Bar"} |
| 115 | +user_repo.update(user) |
| 116 | +# UPDATE users SET name = $1 WHERE id = $2 RETURNING id |
| 117 | +
|
| 118 | +post_repo.delete(posts.first) |
| 119 | +# DELETE FROM posts WHERE id = $1 |
| 120 | +``` |
| 121 | + |
| 122 | +**^1:** Returning IDs is not working for PostgreSQL yet. See https://github.com/will/crystal-pg/issues/112. |
| 123 | + |
| 124 | +## Contributing |
| 125 | + |
| 126 | +1. Fork it ( https://github.com/vladfaust/core.cr/fork ) |
| 127 | +2. Create your feature branch (git checkout -b my-new-feature) |
| 128 | +3. Commit your changes (git commit -am 'Add some feature') |
| 129 | +4. Push to the branch (git push origin my-new-feature) |
| 130 | +5. Create a new Pull Request |
| 131 | + |
| 132 | +## Contributors |
| 133 | + |
| 134 | +- [@vladfaust](https://github.com/vladfaust) Vlad Faust - creator, maintainer |
0 commit comments