forked from anykeyh/clear
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Addition of jsonb helper - Small fixes on multi-connection object
- Loading branch information
Yacine Petitprez
committed
Jun 24, 2018
1 parent
3caf1d6
commit f654938
Showing
22 changed files
with
1,567 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
# Getting started | ||
|
||
TL;DR | ||
|
||
This show the basic features of Clear step by step. | ||
|
||
Feel free to copy paste it into a new crystal file and run it ! | ||
Many things are not covered here (model mutation, validation...), | ||
they are covered in other articles of this wiki ! | ||
|
||
```crystal | ||
# To create the database (comment/uncomment if you want) | ||
`echo "DROP DATABASE IF EXISTS sample_for_wiki;" | psql -U postgres` | ||
`echo "CREATE DATABASE sample_for_wiki;" | psql -U postgres` | ||
# Unleash the kraken !!! | ||
require "../../src/clear" | ||
# Initialize the connection | ||
Clear::SQL.init("postgres://postgres@localhost/sample_for_wiki") | ||
# Setting log level to DEBUG will allow you to see the requests made by the system | ||
Clear.logger.level = ::Logger::DEBUG | ||
# Because it's a step by step tutorial | ||
def pause | ||
puts("You can investigate your terminal... Just press any key to continue!") | ||
gets | ||
end | ||
# Create a migration | ||
# A migration follow naming constraint to keep the migration order; this naming constraint can be file based, or class based | ||
# [NUMBER]_name.cr => Use the filename as constraint for order of the migration | ||
# class ClassName[NUMBER] => Use the number at the end of the class name as constraint for order of the migration | ||
# You can also redefine `uid` in migration, if you don't want to have number in your filename or classname | ||
# Here we are going to use the number into the class, to keep all the source of our application | ||
# into the same file | ||
class FirstMigration1 | ||
include Clear::Migration | ||
# Everything is included in `change` method | ||
def change(dir) # < dir is the direction of the migration, up or down | ||
dir.up { } # This block will be trigger only when we charge a migration into the database ! | ||
create_table "users" do |t| # We create the table users | ||
# By default, a serial bigint named "id" will be created. You can remove this behavior adding `id: false` in create_table parameters | ||
t.string "first_name", index: true, null: false | ||
t.string "last_name", index: true, null: false | ||
t.string "password_encrypted" | ||
t.string "email", unique: true # Add unique constraint | ||
end | ||
create_table "posts" do |t| | ||
# Create a foreign key constraint in PG to users | ||
t.references to: "users", on_delete: "cascade", null: false | ||
t.string "title", index: true # Creation of an index on title. Soon we will be able to use tsvector ! | ||
t.string "content", null: true | ||
t.bool "published", default: false, null: false | ||
end | ||
end | ||
end | ||
# we can write down our models now ! | ||
class User | ||
include Clear::Model | ||
self.table = "users" | ||
# Adding a primary key is mandatory in models | ||
# if you want to use relations ! | ||
with_serial_pkey # By default, the primary key is `id` | ||
column first_name : String | ||
column last_name : String | ||
# Using the column is quite straight forward... | ||
def full_name | ||
[first_name, last_name].join(" ") | ||
end | ||
# Note here, because password_encrypted can be nullable in the database, | ||
# you expect it to be nullable in Clear too. | ||
column password_encrypted : String? | ||
column email : String | ||
# Possible relation are: has_one, has_many, has_many .. through and belongs_to | ||
has_many posts : Post | ||
end | ||
class Post | ||
include Clear::Model | ||
self.table = "posts" | ||
with_serial_pkey | ||
belongs_to user : User | ||
column title : String | ||
column content : String? | ||
column published : Bool | ||
# Scope are helpers on class level, which helps to filter the query and can | ||
# be chained ! | ||
scope(published) { where({published: true}) } | ||
end | ||
# Now we have a migration, it's time to migrate it ! | ||
# Note if you launch your application again, the migration won't be applied again. | ||
Clear::Migration::Manager.instance.apply_all | ||
pause | ||
# Now it's time to put some data, but first, let's delete everything from our database. | ||
# There's multiple way to delete, but the simplest is to use the SQL builder | ||
# (Also like this I can show it to you !) | ||
# Clear offers a great query building interface. | ||
# Ok, let's delete all the users! | ||
# Note: We cannot yet do any "TRUNCATE" operation :(, but soon, I hope ! | ||
Clear::SQL.delete("users").execute | ||
pause | ||
# Since the posts have foreign constraints to users with cascaded delete (see migration) | ||
# we assume the posts are deleted, too ! | ||
# Now it's time to create users and post ! | ||
# But first, let's create a lorem ipsum function ! | ||
def lorem(count) | ||
dictionnary = %w(si dolor es null nunc tempor eros urna vitae malesuada nibh | ||
elementum id class aptent taciti sociosqu ad litora torquent per conubia | ||
nostra per inceptos himenaeos pellentesque molestie vitae nisi vitae ornare | ||
sed condimentum sed arcu non rhoncus mauris fringilla sit amet ligula ut | ||
ultrices aenean maximus enim nec malesuada pellentesque suspendisse hendrerit | ||
dignissim sapien eu malesuada magna laoreet vel) | ||
raise "count must be > 2" unless count > 2 # Always fail fast ! | ||
# This should do the trick! | ||
"Lorem ipsum" + dictionnary.sample(count - 2).join(" ") | ||
end | ||
# Now it's time to create our users ! | ||
# Let's create 200 users ! | ||
200.times do |idx| | ||
# This is the simplest way to create and save the user in one operation | ||
u = User.create!({first_name: "user#{idx}", last_name: "malcom#{idx}", email: "user#{idx}@localhost"}) | ||
# We can then build posts for the users | ||
(1..12).to_a.sample.times do # Each user own 1 to 12 posts ! | ||
p = u.posts.build | ||
# As you can see, the post already have the ID of the user, so no need to make the link manually ! | ||
p.title = lorem(rand(4..8)) | ||
p.content = lorem(rand(10..200)) | ||
p.published = [true, false].sample # Half of our posts are not published; this is to check later with the scope! | ||
p.save! # We can then save the post ! | ||
end | ||
end | ||
# Ok, let's see if I can count all the posts with `enim` (one of the lorem ipsum word!) into the title: | ||
puts Post.query.where { title =~ /(^| )enim( |$)/i }.count | ||
pause | ||
# It wasnt so hard :-). You can notice here I use the Clear's expression engine, idea | ||
# completely stolen to the famous Sequel ORM ! | ||
# We also use the power of postgres to use regex straight to the database ! Yeah ! | ||
# Ok, now we have a filled database, let's show up the posts of our first 10 users ! | ||
# To start a collection gathering, you need to use "query" (or you can use a scope also) | ||
User.query.limit(5).each do |user| | ||
puts "Post of user: #{user.full_name}" | ||
user.posts.each do |p| | ||
puts p.title | ||
end | ||
end | ||
pause | ||
# How wait... It's boring it calls, SQL query for each user ! | ||
# The solution is here ! Let's tell Clear we want the posts of the user! | ||
User.query.with_posts.limit(5).each do |user| | ||
puts "Post of user: #{user.full_name}" | ||
user.posts.each do |p| | ||
puts p.title | ||
end | ||
end | ||
pause | ||
# But wait... I want to show only published posts of the user, how can I do? | ||
# Answer: Filter the posts ! | ||
User.query.with_posts(&.published).limit(5).each do |user| | ||
puts "Post of user: #{user.full_name}" | ||
user.posts.each do |p| | ||
puts p.title | ||
end | ||
end | ||
pause | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
Welcome to the Clear! | ||
|
||
Clear is currently in alpha stage, but already used by the author in some applications. | ||
A lot of features have yet to be discovered, don't hesitate to make a pull request if something is impossible now, so I'll fix it. | ||
|
||
If you're in hurry and already at ease with Active Record pattern, a simple stand-alone example will helps you understand the fundamentals of Clear: | ||
|
||
|
||
## Getting started | ||
|
||
- [Simple Example](BasicExample.md) | ||
- [Setup of the ORM](Setup.md) | ||
|
||
## Model | ||
|
||
- [Model definition](model/Definition.md) | ||
- [Handling Model Lifecycle](model/Lifecycle.md) | ||
- [Model relation](model/Relation.md) | ||
- [Model validation](model/Validation.md) | ||
- [Event Hooks](model/Hooks.md) | ||
- [Migrations](migration/Migration.md) | ||
- [Polymorphism](model/Polymorphism.md) | ||
|
||
## Querying | ||
|
||
- [Aggregate functions](querying/Aggregate.md) | ||
- [Scopes](querying/Scopes.md) | ||
- [Fetching extra columns](querying/ExtraColumns.md) | ||
- [Expression engine](querying/ExpressionEngine.md) | ||
- [Usage of SQL builder](querying/RequestBuilding.md) | ||
- [Transactions And savepoints](querying/Transaction.md) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
# Installation | ||
|
||
In `shards.yml`, please add theses lines: | ||
|
||
```yaml | ||
dependencies: | ||
clear: | ||
github: anykeyh/clear | ||
``` | ||
Then in your code, add the requirement: | ||
```crystal | ||
require "clear" | ||
``` | ||
|
||
# Initialization | ||
|
||
Connection can be initialized by `Clear::SQL.init(uri)` method: | ||
|
||
```crystal | ||
Clear::SQL.init("postgres://[USER]:[PASSWORD]@[HOST]/[DATABASE]") | ||
``` | ||
|
||
You can fine tune your connection, accessible by the `Clear::SQL.connection` object. Check the [will/crystal-pg](https://github.com/will/crystal-pg) shards documentations for more informations. | ||
|
||
Additional parameters like `retry_attempts`, `retry_delay` etc... can be passed to the URI. Please check [the official db documentation](https://crystal-lang.org/docs/database/connection_pool.html) | ||
|
||
By choice, Clear doesn't offers any configuration file (e.g. `database.yml`), so you need to setup your own architecture to handle different build targets. | ||
|
||
|
||
# Logging | ||
|
||
By default, logging of SQL output is disabled. To enable it, you need to change the logger verbosity level: | ||
|
||
```crystal | ||
Clear.logger.level = ::Logger::DEBUG | ||
``` |
Oops, something went wrong.