Skip to content

Commit

Permalink
GITBOOK-21: No subject
Browse files Browse the repository at this point in the history
  • Loading branch information
gitbook-bot committed Aug 21, 2024
1 parent bfa2f04 commit e884628
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 16 deletions.
12 changes: 12 additions & 0 deletions docs/.gitbook/assets/Untitled-5.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
* [Repository](coreconcepts/patterns/repository.md)
* [Guides](guides/README.md)
* [Active Record with Cql](guides/active-record-with-cql/README.md)
* [CQL Record Models](guides/active-record-with-cql/cql-record-models.md)
* [BelongsTo](guides/active-record-with-cql/belongsto.md)
* [HasOne](guides/active-record-with-cql/hasone.md)
* [HasMany](guides/active-record-with-cql/hasmany.md)
Expand Down
23 changes: 12 additions & 11 deletions docs/guides/active-record-with-cql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,18 @@ Replace the database connection string with your actual PostgreSQL or MySQL cred

CQL uses migrations to manage the database schema, similar to Rails' ActiveRecord or Ecto's migrations.

1. **Generate a migration**: CQL provides generators to create migration files.
1. **Create a migration**: Let's define a `users` table in this migration:

```bash
crystal lib/cql/bin/cql generate migration CreateUsers
```

This will create a file in the `db/migrate` directory, for example, `20230817000000_create_users.cr`.
2. **Edit the migration**: Let's define a `users` table in this migration:
\
Create a file in the `db/migrate` directory, for example, `20230817000000_create_users.cr`\
 

```crystal
# db/migrate/20230817000000_create_users.cr
class CreateUsers < CQL::Migration
self.version = 20230817000000
def up
schema.alter :users do
text :name, null: false
Expand All @@ -84,11 +83,13 @@ CQL uses migrations to manage the database schema, similar to Rails' ActiveRecor
end
```

Here, we create a `users` table with columns `name`, `email`, and `timestamps`.
3. **Run the migration**: After defining your migration, you can run it with:
Here, we create a `users` table with columns `name`, `email`, and `timestamps`.

```bash
crystal lib/cql/bin/cql migrate
1. **Run the migration**: After defining your migration, you can run it with:\


```crystal
AcmeDB.migrator.up
```

This command will execute all pending migrations and update your database schema.
Expand Down
346 changes: 346 additions & 0 deletions docs/guides/active-record-with-cql/cql-record-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
# CQL Record Models

The `Cql::Record` module in the CQL toolkit is a crucial part of the Object-Relational Mapping (ORM) system in Crystal. It allows you to define models that map to tables in your database and provides a wide array of functionalities for querying, inserting, updating, and deleting records. In this guide, we'll explore how the `Cql::Record` module works and how to use it effectively.

### **What is the `Record` Module?**

The `Cql::Record` module is a mixin that provides your Crystal structs with the ability to interact with database tables, treating them as Active Record-style models. This means that each model represents a table in your database, and each instance of that model represents a row within that table.

***

## Basic Setup: Defining a Schema

To start working with CQL models, you first need to define your database schema and map models (Crystal structs) to tables within that schema.

### **Example Schema**

Let's assume we have two tables: `posts` and `comments`. Each post can have many comments, and each comment belongs to one post.

```crystal
AcmeDB = Cql::Schema.define(
:acme_db,
adapter: Cql::Adapter::Postgres,
uri: "postgresql://example:example@localhost:5432/example"
) do
table :posts do
primary :id, Int64, auto_increment: true
text :title
text :body
timestamp :published_at
end
table :comments do
primary :id, Int64, auto_increment: true
bigint :post_id
text :body
end
end
```

* **posts table**: Contains columns `id`, `title`, `body`, and `published_at`.
* **comments table**: Contains columns `id`, `post_id` (foreign key), and `body`.

***

## Defining Models with `Record`

Now, let's define the `Post` and `Comment` models that map to the `posts` and `comments` tables.

### **Post Model**

```crystal
struct Post
include Cql::Record(Post, Int64)
define AcmeDB, :posts
getter id : Int64?
getter title : String
getter body : String
getter published_at : Time
# Initializing a new post
def initialize(@title : String, @body : String, @published_at : Time = Time.utc)
end
end
```

### **Comment Model**

```crystal
struct Comment
include Cql::Record(Comment, Int64)
define AcmeDB, :comments
getter id : Int64?
getter post_id : Int64
getter body : String
# Initializing a new comment
def initialize(@post_id : Int64, @body : String)
end
# Each comment belongs to a post
belongs_to :post, Post
end
```

### Key Features of the `Record` Module

The `Cql::Record` module adds several useful methods and features to your model:

**1. Defining Models with `define`**

Each model must be linked to a schema and a table using the `define` method.

```crystal
define AcmeDB, :posts
```

This associates the `Post` struct with the `posts` table in the `AcmeDB` schema.

## **Querying Records**

The `Record` module provides convenient methods for querying the database.

* **Fetching All Records**:

```crystal
posts = Post.all
```

This retrieves all the records from the `posts` table.

* **Fetching a Record by ID**:

```crystal
post = Post.find(1)
```

This retrieves the post with ID `1`. If the record is not found, `nil` is returned.

* **Fetching the First or Last Record**:

```crystal
first_post = Post.first
last_post = Post.last
```

These methods fetch the first and last records in the table, respectively.

* **Fetching Records with Conditions**:

```crystal
active_posts = Post.find_all_by(active: true)
post_by_title = Post.find_by(title: "My First Post")
```

These methods allow you to filter records by specific fields.

## **Creating Records**

You can create new records using the `create` method.

```crystal
post_id = Post.create(title: "New Post", body: "This is a new post")
```

This creates a new post and returns the `id` of the newly created record.

## **Updating Records**

You can update existing records by passing the record’s `id` and the fields to update.

```crystal
Post.update(1, title: "Updated Post Title")
```

This updates the post with ID `1` to have the new title "Updated Post Title".

## **Deleting Records**

To delete records, you can use the `delete` method:

```crystal
Post.delete(1)
```

This deletes the post with ID `1`.

## **Associations**

The `Record` module also allows you to define associations between models. In our example, we defined a `belongs_to` relationship in the `Comment` model:

```crystal
belongs_to :post, Post
```

This means that each comment is associated with one post.

You can also define other associations like `has_many` and `has_one`:

```crystal
has_many :comments, Comment
```

This would go into the `Post` model to define that each post can have multiple comments.

***

## Instance-Level Methods for Records

The `Record` module also provides instance-level methods for interacting with individual records:

### **Saving Records**

To insert a new record into the database or update an existing one, you can use the `save` method:

```crystal
post = Post.new("My Title", "My Content")
post.save
```

If the record has an `id`, it will update the record. Otherwise, it will create a new record.

### **Updating Records**

You can also update specific fields on an existing record using the `update` method:

```crystal
post.update(title: "Updated Title")
```

### **Deleting Records**

To delete a record from the database:

```crystal
post.delete
```

This deletes the current record.

### &#x20;**Reloading Records**

You can reload the current state of the record from the database using `reload!`:

```crystal
post.reload!
```

This updates the attributes of the record with the latest values from the database.

### **Working with Attributes**

You can access and manipulate the record’s attributes using the `attributes` method:

```crystal
attrs = post.attributes
# => {id: 1, title: "My Title", body: "My Content"}
```

You can also set the attributes:

```crystal
post.attributes {title: "New Title", body: "Updated Content"}
```

***

## Example: Building a Simple Blog System

Let's combine everything we've learned to build a simple blog system where posts can have many comments.

### **Defining the Schema**:

```crystal
AcmeDB = Cql::Schema.define(
:acme_db,
adapter: Cql::Adapter::Postgres,
uri: "postgresql://example:example@localhost:5432/example"
) do
table :posts do
primary
text :title
text :body
timestamp :published_at
end
table :comments do
primary
bigint :post_id
text :body
end
end
```

### **Defining the Models**:

```crystal
struct Post
include Cql::Record(Post, Int64)
define AcmeDB, :posts
getter id : Int64?
getter title : String
getter body : String
getter published_at : Time
has_many :comments, Comment
def initialize(@title : String, @body : String, @published_at : Time = Time.utc)
end
end
struct Comment
include Cql::Record(Comment, Int64)
define AcmeDB, :comments
getter id : Int64?
getter post_id : Int64
getter body : String
belongs_to :post, Post
def initialize(@post_id : Int64, @body : String)
end
end
```

### **Using the Models**:

* **Creating a Post**:

```crystal
post = Post.new("My First Blog Post", "This is the content of my first blog post.")
post.save
```

* **Adding Comments to the Post**:

```crystal
comment = Comment.new(post.id.not_nil!, "This is a comment.")
comment.save
```

* **Fetching Comments for a Post**:

```crystal
post = Post.find(1)
comments = post.comments
```

***

## Conclusion

The `Cql::Record` module provides powerful tools for working with database records in a Crystal application. It simplifies the process of defining models, querying records, and managing associations. By leveraging the capabilities of CQL's Active Record-style ORM, you can build complex applications with ease.

With `Cql::Record`, you have access to:

* Easy schema and model definition.
* A rich set of query and manipulation methods.
* Powerful association handling (`belongs_to`, `has_many`,
Loading

0 comments on commit e884628

Please sign in to comment.