|
4 | 4 |
|
5 | 5 | [](https://crystal-lang.org/) |
6 | 6 | [](https://travis-ci.org/onyxframework/sql) |
| 7 | +[](https://docs.onyxframework.org/sql) |
7 | 8 | [](https://api.onyxframework.org/sql) |
8 | 9 | [](https://github.com/onyxframework/sql/releases) |
9 | 10 |
|
10 | | -An MIT-licensed SQL ORM for [Crystal](https://crystal-lang.org). |
11 | | - |
12 | | -## Supporters ❤️ |
13 | | - |
14 | | -Thanks to all my patrons, I can continue working on beautiful Open Source Software! 🙏 |
15 | | - |
16 | | -[Lauri Jutila](https://github.com/ljuti), [Alexander Maslov](https://seendex.ru), Dainel Vera |
17 | | - |
18 | | -*You can become a patron too in exchange of prioritized support and other perks* |
19 | | - |
20 | | -<a href="https://www.patreon.com/vladfaust"><img height="50" src="https://onyxframework.org/img/patreon-button.svg"></a> |
| 11 | +A deligtful SQL ORM. |
21 | 12 |
|
22 | 13 | ## About 👋 |
23 | 14 |
|
24 | | -Onyx::SQL is an SQL ORM for the [Crystal Language](https://crystal-lang.org). It features handy schema definition DSL and powerful type-safe query builder. It preserves composition and has a decent API documentation. |
25 | | - |
26 | | -It is a part of [Onyx Framework](https://onyxframework.org), but it is **not** strictly tied to it. You absolutely can use this ORM with a web framework other than [Onyx::HTTP](https://github.com/onyxframework/http) and [Onyx::REST](https://github.com/onyxframework/rest). |
27 | | - |
28 | | -It implements the [crystal-db](https://github.com/crystal-lang/crystal-db) API, which makes it usable with any SQL database! It has been successfully tested with the following DBs: |
29 | | - |
30 | | -- [x] SQLite3 |
31 | | -- [x] PostgreSQL |
32 | | -- [ ] MySQL (*coming soon*) |
33 | | - |
34 | | -This ORM, as all other Onyx components, targets to be easily understandble for newcomers, but be able to grow with a developers's knowledge. Fundamentally, it relies on extremely powerful Crystal annotations, but they may be tedious for daily tasks, that why they're hidden by default under the convenient schema DSL. See the examples below. |
| 15 | +Onyx::SQL is a deligthful database-agnostic SQL ORM for [Crystal language](https://crystal-lang.org/). It features a convenient schema definition DSL, type-safe SQL query builder, clean architecture with Repository and more! |
35 | 16 |
|
36 | 17 | ## Installation 📥 |
37 | 18 |
|
38 | | -Add this to your application's `shard.yml`: |
| 19 | +Add these lines to your application's `shard.yml`: |
39 | 20 |
|
40 | 21 | ```yaml |
41 | 22 | dependencies: |
| 23 | + onyx: |
| 24 | + github: onyxframework/onyx |
| 25 | + version: ~> 0.2.0 |
42 | 26 | onyx-sql: |
43 | 27 | github: onyxframework/sql |
44 | | - version: ~> 0.6.2 |
| 28 | + version: ~> 0.6.0 |
45 | 29 | ``` |
46 | 30 |
|
47 | | -This shard follows [Semantic Versioning v2.0.0](http://semver.org/), so check [releases](https://github.com/onyxframework/sql/releases) and change the `version` accordingly. Please visit [github.com/crystal-lang/shards](https://github.com/crystal-lang/shards) to know more about Crystal shards. |
| 31 | +This shard follows [Semantic Versioning v2.0.0](http://semver.org/), so check [releases](https://github.com/onyxframework/rest/releases) and change the `version` accordingly. Please visit [github.com/crystal-lang/shards](https://github.com/crystal-lang/shards) to know more about Crystal shards. |
48 | 32 |
|
49 | | -You'd also need to add a database dependency conforming the [crystal-db](https://github.com/crystal-lang/crystal-db) interface. For example, [pg](https://github.com/will/crystal-pg): |
| 33 | +You'd also need to add a database dependency conforming to the [crystal-db](https://github.com/crystal-lang/crystal-db) interface. For example, [pg](https://github.com/will/crystal-pg): |
50 | 34 |
|
51 | | -```yaml |
| 35 | +```diff |
52 | 36 | dependencies: |
| 37 | + onyx: |
| 38 | + github: onyxframework/onyx |
| 39 | + version: ~> 0.2.0 |
53 | 40 | onyx-sql: |
54 | 41 | github: onyxframework/sql |
55 | 42 | version: ~> 0.6.0 |
56 | | - pg: |
57 | | - github: will/crystal-pg |
58 | | - version: ~> 0.15.0 |
| 43 | ++ pg: |
| 44 | ++ github: will/crystal-pg |
| 45 | ++ version: ~> 0.15.0 |
59 | 46 | ``` |
60 | 47 |
|
61 | 48 | ## Usage 💻 |
62 | 49 |
|
63 | | -The API docs are hosted at <https://api.onyxframework.org/sql>, and they're pretty comprehensive. Don't hesistate to read them all after you're done with this section! |
64 | | - |
65 | | -It's a good idea to get yourself familiar with the [Crystal DB docs](https://crystal-lang.org/reference/database/) before moving on. |
66 | | - |
67 | | -### 101 📖 |
68 | | - |
69 | | -As any other ORM, Onyx::SQL allows to define models which will be mapped to SQL tables. Assuming that you have the following table in a PostgreSQL database: |
| 50 | +For this PostgreSQL table: |
70 | 51 |
|
71 | 52 | ```sql |
72 | 53 | CREATE TABLE users ( |
73 | | - id SERIAL PRIMARY KEY, |
74 | | - name TEXT NOT NULL |
| 54 | + id SERIAL PRIMARY KEY, |
| 55 | + name TEXT NOT NULL |
| 56 | + created_at TIMESTAMPTZ NOT NULL DEFAULT now() |
75 | 57 | ); |
76 | 58 | ``` |
77 | 59 |
|
78 | | -> **Note:** Onyx::SQL does not provide any tools for migrations. Check [migrate.cr](https://github.com/vladfaust/migrate.cr) for a production-ready solution. |
79 | | - |
80 | | -Then in your code you would do: |
| 60 | +Define the user schema: |
81 | 61 |
|
82 | 62 | ```crystal |
83 | | -require "pg" |
84 | | -require "onyx-sql" |
| 63 | +require "onyx/sql" |
85 | 64 |
|
86 | 65 | class User |
87 | 66 | include Onyx::SQL::Model |
88 | 67 |
|
89 | 68 | schema users do |
90 | 69 | pkey id : Int32 |
91 | | - type name : String |
| 70 | + type name : String, not_null: true |
| 71 | + type created_at : Time, not_null: true, default: true |
92 | 72 | end |
93 | 73 | end |
94 | | -
|
95 | | -db = DB.open("postgresql://postgres:postgres@localhost:5432/my_db") |
96 | | -
|
97 | | -user = User.new(name: "John") |
98 | | -rs = db.query(*user.insert.returning(User).build(true)) |
99 | | -
|
100 | | -users = User.from_rs(rs) |
101 | | -user = users.first |
102 | | -
|
103 | | -pp user.id # => 1 |
104 | | -``` |
105 | | - |
106 | | -Congratulations, you've successfully inserted a brand new User instance! :tada: |
107 | | - |
108 | | -> **Note:** `.build(true)` is required instead of simple `.build` because PostgreSQL has different syntax for query arguments (`$n` instead of `?`). |
109 | | - |
110 | | -#### Querying |
111 | | - |
112 | | -```crystal |
113 | | -rs = db.query("SELECT * FROM users WHERE id = $1", 1) |
114 | | -user = User.from_rs(rs).first |
115 | | -
|
116 | | -pp user # => <User @id=1 @name="John"> |
117 | | -``` |
118 | | - |
119 | | -#### Updating |
120 | | - |
121 | | -You can easily update models with the [`Changeset`](https://api.onyxframework.org/sql/Onyx/SQL/Model/Changeset.html) concept: |
122 | | - |
123 | | -```crystal |
124 | | -changeset = user.changeset |
125 | | -changeset.update(name: "Jake") |
126 | | -
|
127 | | -db.exec(*user.update(changeset).build(true)) |
128 | 74 | ``` |
129 | 75 |
|
130 | | -#### Deletion |
131 | | - |
132 | | -Deleting from DB is simple as well: |
| 76 | +Insert a new user instance: |
133 | 77 |
|
134 | 78 | ```crystal |
135 | | -db.exec(*user.delete.build(true)) |
136 | | -``` |
137 | | - |
138 | | -### Repository |
139 | | - |
140 | | -Onyx::SQL has the [`Onyx::SQL::Repository`](https://api.onyxframework.org/sql/Onyx/SQL/Repository.html) class, which effectively wraps the database connection with logging, automatically builds queries and more: |
141 | | - |
142 | | -```crystal |
143 | | -repo = Onyx::SQL::Repository.new(db) |
144 | | -
|
145 | | -user = User.new(name: "Archer") |
146 | | -user = repo.query(user.insert.returning(User)).first |
147 | | -
|
148 | | -# [postgresql] INSERT INTO users (name) VALUES (?) |
149 | | -# 1.234ms |
150 | | -``` |
151 | | - |
152 | | -### Query |
153 | | - |
154 | | -[`Onyx::SQL::Query`](https://api.onyxframework.org/sql/Onyx/SQL/Query.html) is a powerful type-safe SQL query builder with almost all SQL methods implemented: |
| 79 | +user = User.new(name: "John") |
| 80 | +user = Onyx.query(user.insert.returning("*")).first |
155 | 81 |
|
156 | | -```crystal |
157 | | -query = User.select(:name).where(id: 2) |
158 | | -pp query # => <Onyx::SQL::Query(User) ...> |
159 | | -pp query.build # => {"SELECT users.name FROM users WHERE id = ?", {2}} |
| 82 | +pp user # => #<User @id=1, @name="John", @created_at=#<Time ...>> |
160 | 83 | ``` |
161 | 84 |
|
162 | | -`Query` is just an object which could be expanded into a pair of SQL string a query params. You can then use it however you want: |
| 85 | +Query the user: |
163 | 86 |
|
164 | 87 | ```crystal |
165 | | -sql, params = query.build(true) |
166 | | -rs = db.query(sql, params) |
167 | | -
|
168 | | -# Or shorter |
169 | | -rs = db.query(*query.build(true)) |
170 | | -
|
171 | | -# Or with repository |
172 | | -user = repo.query(query).first |
| 88 | +user = Onyx.query(User.where(id: 1)).first? |
173 | 89 | ``` |
174 | 90 |
|
175 | | -### References |
176 | | - |
177 | | -Onyx::SQL has a native support for model references, both direct and foreign ones. |
| 91 | +With another PostgreSQL table: |
178 | 92 |
|
179 | 93 | ```sql |
180 | 94 | CREATE TABLE posts ( |
181 | 95 | id SERIAL PRIMARY KEY, |
182 | | - author_id INT NOT NULL REFERENCES users (id), |
183 | | - content TEXT NOT NULL, |
| 96 | + author_id INT NOT NULL REFERENCES users(id), |
| 97 | + content TEXT NOT NULL |
184 | 98 | created_at TIMESTAMPTZ NOT NULL DEFAULT now() |
185 | 99 | ); |
186 | 100 | ``` |
187 | 101 |
|
| 102 | +Define the post schema: |
| 103 | + |
188 | 104 | ```crystal |
189 | | -class User |
| 105 | +class Post |
190 | 106 | include Onyx::SQL::Model |
191 | 107 |
|
192 | | - schema users do |
| 108 | + schema posts do |
193 | 109 | pkey id : Int32 |
194 | | - type name : String |
195 | | - type authored_posts : Array(Post), foreign_key: "author_id" |
| 110 | + type author : User, not_null: true, key: "author_id" |
| 111 | + type content : String, not_null: true |
| 112 | + type created_at : Time, not_null: true, default: true |
196 | 113 | end |
197 | 114 | end |
| 115 | +``` |
198 | 116 |
|
199 | | -class Post |
| 117 | +Add the posts reference to the user schema: |
| 118 | + |
| 119 | +```diff |
| 120 | +class User |
200 | 121 | include Onyx::SQL::Model |
201 | 122 |
|
202 | | - schema posts do |
| 123 | + schema users do |
203 | 124 | pkey id : Int32 |
204 | | - type content : String |
205 | | - type author : User, key: "author_id" |
| 125 | + type name : String, not_null: true |
| 126 | + type created_at : Time, not_null: true, default: true |
| 127 | ++ type authored_posts : Array(Post), foreign_key: "author_id" |
206 | 128 | end |
207 | 129 | end |
208 | | -
|
209 | | -user = User.new(id: 2, name: "Archer") |
210 | | -post = Post.new(content: "Classic", author: user) |
211 | | -repo.exec(post.insert) |
212 | | -
|
213 | | -# [postgresql] INSERT INTO posts (content, author_id) VALUES (?, ?) |
214 | | -# The actual DB arguments are "Classic" and 2 |
215 | 130 | ``` |
216 | 131 |
|
217 | | -Thanks to the `Query` builder, it is possible to build powerful type-safe joins in no time: |
| 132 | +Create a new post: |
218 | 133 |
|
219 | 134 | ```crystal |
220 | | -posts = repo.query(Post |
221 | | - .select(:id, :content) |
222 | | - .join(author: true) do |q| |
223 | | - q.select(:id, :name) |
224 | | - q.where(name: "Archer") |
225 | | - end) |
226 | | -
|
227 | | -pp posts.first # <Post @id=1 @content="Classic" @author=<User @id=2 @name="Archer">> |
| 135 | +user = User.new(id: 1) |
| 136 | +post = Post.new(author: user, content: "Hello, world!") |
| 137 | +Onyx.exec(post.insert) |
228 | 138 | ``` |
229 | 139 |
|
230 | | -### Macros |
231 | | - |
232 | | -[Onyx top-level macros](https://github.com/onyxframework/onyx#sql) allow to define top-level repository methods: |
| 140 | +Query all the posts by a user with name "John": |
233 | 141 |
|
234 | 142 | ```crystal |
235 | | -require "onyx/env" |
236 | | -require "onyx/sql" |
| 143 | +posts = Onyx.query(Post |
| 144 | + .join(author: true) do |x| |
| 145 | + x.select(:id, :name) |
| 146 | + x.where(name: "John") |
| 147 | + end |
| 148 | +) |
237 | 149 |
|
238 | | -Onyx.query # Singleton Onyx::SQL::Repository.query call |
239 | | -Onyx.exec # ditto |
240 | | -Onyx.scalar # ditto |
| 150 | +posts.first # => #<Post @id=1, @author=#<User @id=1 @name="John">, @content="Hello, world!"> |
241 | 151 | ``` |
242 | 152 |
|
243 | | -### Next steps |
244 | | - |
245 | | -That's all for this README! Jump to the API docs at <https://api.onyxframework.org/sql> or explore the [Crystal World](https://github.com/vladfaust/crystalworld) application built with Onyx, which is greatly documented as well! |
246 | | - |
247 | | -Direct API links: |
248 | | - |
249 | | -* [`Onyx::SQL::Model`](https://api.onyxframework.org/sql/Onyx/SQL/Model.html) |
250 | | -* [`Onyx::SQL::Query`](https://api.onyxframework.org/sql/Onyx/SQL/Query.html) |
251 | | -* [`Onyx::SQL::Repository`](https://api.onyxframework.org/sql/Onyx/SQL/Repository.html) |
| 153 | +To know more about Onyx::SQL, please visit [docs.onyxframework.org/sql](https://docs.onyxframework.org/sql) 📚 |
252 | 154 |
|
253 | 155 | ## Community 🍪 |
254 | 156 |
|
255 | | -There are multiple places to talk about this particular shard and about other ones as well: |
| 157 | +There are multiple places to talk about Onyx: |
256 | 158 |
|
257 | | -* [Onyx::SQL Gitter chat](https://gitter.im/onyxframework/sql) |
258 | | -* [Onyx Framework Gitter community](https://gitter.im/onyxframework) |
259 | | -* [Vlad Faust Gitter community](https://gitter.im/vladfaust) |
260 | | -* [Onyx Framework Twitter](https://twitter.com/onyxframework) |
261 | | -* [Onyx Framework Telegram channel](https://telegram.me/onyxframework) |
| 159 | +* [Gitter](https://gitter.im/onyxframework) |
| 160 | +* [Twitter](https://twitter.com/onyxframework) |
262 | 161 |
|
263 | | -## Support ❤️ |
| 162 | +## Support 🕊 |
264 | 163 |
|
265 | 164 | This shard is maintained by me, [Vlad Faust](https://vladfaust.com), a passionate developer with years of programming and product experience. I love creating Open-Source and I want to be able to work full-time on Open-Source projects. |
266 | 165 |
|
267 | | -I will do my best to answer your questions in the free communication channels above, but if you want prioritized support, then please consider becoming my patron. Your issues will be labeled with your patronage status, and if you have a sponsor tier, then you and your team be able to communicate with me in private or semi-private channels such as e-mail and [Twist](https://twist.com). There are other perks to consider, so please, don't hesistate to check my Patreon page: |
| 166 | +I will do my best to answer your questions in the free communication channels above, but if you want prioritized support, then please consider becoming my patron. Your issues will be labeled with your patronage status, and if you have a sponsor tier, then you and your team be able to communicate with me privately in [Twist](https://twist.com). There are other perks to consider, so please, don't hesistate to check my Patreon page: |
268 | 167 |
|
269 | 168 | <a href="https://www.patreon.com/vladfaust"><img height="50" src="https://onyxframework.org/img/patreon-button.svg"></a> |
270 | 169 |
|
271 | | -You could also help me a lot if you leave a star to this GitHub repository and spread the world about Crystal and Onyx! 📣 |
| 170 | +You could also help me a lot if you leave a star to this GitHub repository and spread the word about Crystal and Onyx! 📣 |
272 | 171 |
|
273 | 172 | ## Contributing |
274 | 173 |
|
275 | | -1. Fork it ( https://github.com/onyxframework/http/fork ) |
| 174 | +1. Fork it ( https://github.com/onyxframework/sql/fork ) |
276 | 175 | 2. Create your feature branch (git checkout -b my-new-feature) |
277 | 176 | 3. Commit your changes (git commit -am 'feat: some feature') using [Angular style commits](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit) |
278 | 177 | 4. Push to the branch (git push origin my-new-feature) |
|
0 commit comments