Skip to content

Commit

Permalink
- Revamping of the documents
Browse files Browse the repository at this point in the history
- Addition of jsonb helper
- Small fixes on multi-connection object
  • Loading branch information
Yacine Petitprez committed Jun 24, 2018
1 parent 3caf1d6 commit f654938
Show file tree
Hide file tree
Showing 22 changed files with 1,567 additions and 8 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# v0.1.4alpha

## Breaking changes
- Migration to crystal 0.25.

## Bug fixes
- Fix #13 Calling count on paginated query
- Fix #17 and implement `group_by` method.

## Features
- SQL Builder can now be used with model collection as subqueries.
- Add methods for pagination (PR #16, thanks @jwoertink)
- Add multi-connections system (PR #18, thanks @russ)
- Add JSONB helpers
- Migrating the wiki to the sources of the project, to make easy to have PR for
updating the documentation !

# v0.1.3alpha

Expand Down
192 changes: 192 additions & 0 deletions manual/BasicExample.md
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
```
32 changes: 32 additions & 0 deletions manual/README.md
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)

38 changes: 38 additions & 0 deletions manual/Setup.md
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
```
Loading

0 comments on commit f654938

Please sign in to comment.