Skip to content

Commit

Permalink
Docs goodies #1 (FCO#398)
Browse files Browse the repository at this point in the history
* Replace near-empty index with the introduction

* Add a draft for the first tutorial

* Small doc fixes
  • Loading branch information
Altai-man authored and FCO committed Sep 14, 2019
1 parent 7750c96 commit 5ea5d85
Show file tree
Hide file tree
Showing 15 changed files with 336 additions and 114 deletions.
206 changes: 206 additions & 0 deletions docs/api/tutorials/start.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
## Red starter tutorial

This document is an introduction tutorial which shows the most basic usage examples of Red.
For more in-depth introduction about Red architecture visit [Red architecture](tutorials/architecture) page.

### Models and tables

Red is an Object-Relational Mapping (ORM) tool for Perl 6.

Simply speaking, it allows you to "hide" the layer of interaction
with your relational database and work with Perl 6 objects instead.

Currently, PostgreSQL and SQLite3 relation databases are supported.

Let's start with a simple table:

```sql
CREATE TABLE person(
id integer NOT NULL PRIMARY KEY AUTOINCREMENT,
name varchar(255) NOT NULL
);
```

With this query executed in your database console, a table named `person` was created with
two columns: `id` which is an integer ID of the record, it is a primary key and is
incremented automatically, and `name` which is a string of maximum 255 characters, which is not null.

In Red, each table is represented using a special type of class called "model". It can do
everything what a usual class can do, but also helps you to interact with your table.

Red models use `model` keyword instead of `class`:

```perl6
model Person {
has Int $.id is serial;
has Str $.name is column is rw;
}

my $*RED-DB = database SQLite;
```

We described a model called `Person`. The first attribute `$.id` is typed to be `Int`
and is marked with `is serial` trait. This trait marks the column as a primary one with
autoincrement enabled. The next attribute `$.name` is marked with `is column` trait, which
means this attribute will be mapped onto a column in the table, and is typed as Str.

Note we don't need to specify that the column is not nullable, as it is the default.

The second statement specifies a database to work with. In this case,
an in-memory SQLite database is used, which means all changes will be lost after
the script termination. To avoid this, we can specify a name for the database file:

```perl6
my $*RED-DB = database SQLite, database => 'test.sqlite3'; # Now a file `test.sqlite3` will be created
```

Next, we need to create a table itself:


```perl6
Person.^create-table;
```

Methods marked with `^` are called "meta methods" and are used in Red
for all kinds of operations on models. In this case, calling `^create-table`
creates a table with name `person`.

### Insertion of new records

Let's insert a new record into it. In SQL it could be:

```sql
INSERT INTO person (name) VALUES 'John';
```

In Red we can express it this way:

```perl6
my $person = Person.^create(name => 'John');
```

We call the `^create` method on type object `Person` and assign the result
to the `$person` variable. The assignment is not necessary:

```perl6
Person.^create(name => 'John');
```

The `^create` method returns the created object to work with, though
this result can be simply ignored.

The `$.id` attribute is auto-generated and there is no need to specify it,
while `$.name` attribute must not be null, so we have to specify it:

```perl6
Person.^create; # error
```

### Update of records

Let's try to update our record. In SQL it could be:

```sql
UPDATE person SET name = ‘John Doe’ WHERE id = 1;
```

To do the same in Red, we use setters and a call to `^save`:

```perl6
$person.name = 'John Doe';
$person.^save;
```

All changes to an object that represents the record are lazy,
which means the database connection is not used until the `^save`
method is called.

The method `^save` is useful not only for UPDATE operation, but it can be used on
INSERT too:

```perl6
my $person2 = Person.new(name => 'Joan'); # ^create is not used
$person2.^save; # does INSERT, not UPDATE
```

### Selecting records

Lets add some more records:

```perl6
Person.new(name => "Paul"); # Method call with parentheses and an arrow pair
Person.new: :name<Miki>; # Semicolon form of method call is used
```

Lets begin with selecting all records of the table:

```sql
SELECT * FROM person;
```

In Red, `^all` method is used:

```perl6
for Person.^all -> $person { say $person }
```

Method `^all` returns an instance of class `Seq` that is a lazy sequence of records returned.

```sql
SELECT * FROM person WHERE person.name like 'Jo%';
```

The query above selects all records where name starts with 'Jo'. In Red, you can use Perl 6 `grep`
method to specify clauses of the select query:

```perl6
for Person.^all.grep(*.name.starts-with('Jo')) -> $person { say $person }
```

Note that this call chain will result into an equivalent of the SQL code above,
filtering values happens at the database level, not at Perl 6 level.

```sql
SELECT * FROM person WHERE person.name like 'Jo%' AND person.id = 2;
```

To express the query above, calls to `grep` can be combined:

```perl6
for Person.^all.grep(*.name.starts-with('Jo')).grep(*.id == 2) -> $person { say $person }
```

### Selecting a single record

To get a single record, `^load` method is used. It accepts an arbitrary number of pairs
describing the object properties for the WHERE clause of a SELECT statement. One difference
between using `^all` and `^load` is that the `^load` method returns either a value or a `Nil`
if there are no fitting records, while `^all` returns a `Seq` that might come
with an arbitrary number of elements. The second difference is that `^all` can express
various SELECT statements, while `^load` is restricted to work with columns marked as PRIMARY
and UNIQUE only.

```perl6
say Person.^load(id => 4); # correct
# when the primary column is unambiguous, only its value can be passed
say Person.^load(4); # correct, same as `id => 4`
# however, `^load does not work for non-primary columns:
say Person.^load(:name<Foo>); # error
```

### Deleting rows

To delete rows, the `^delete` method is used. It can be called on an individual
object or on a model to delete all records:

```perl6
# DELETE FROM person WHERE person.id = 42;
$p.^delete;
# DELETE FROM person;
Person.^delete;
```

Here, we covered basics of Red usage. Refer to Red cookbook
for different examples without a particular order or visit the next
tutorial in this series, related to expressing table relationships
using Red, [here](tutorials/relationships).
75 changes: 72 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,72 @@
- [introduction](introduction)
- [general](https://github.com/FCO/Red/wiki)
- [API](API)
### Red ORM documentation

Welcome to Red ORM documentation.

#### Tutorials

If you are looking for tutorials, you can visit:

* [Beginner tutorial](tutorials/start)
* Relationships tutorial
* Red CLI
* Working with PostgreSQL
* Working with SQLite
* Red Cookbook

#### API documentation

If you are looking for pure API docs, here you go:

* [API Index page](API)

#### Wiki

More examples can be found at the project [Wiki](https://github.com/FCO/Red/wiki) page.

#### For developers

* [Red architecture](tutorials/architecture)
* How to create a new driver
* How to create a new cache

#### How to contribute to documentation

##### I want to document something

To document an entity of Red itself (class, operator,
routine etc), do it as Pod6 in the source file. For example, before:

```perl6
sub prefix:<Σ>( *@number-list ) is export {
[+] @number-list
}
```

After:

```perl6
#| A prefix operator that sums numbers
#| It accepts an arbitrary length list of numbers and returns their sum
#| Example: say Σ (13, 16, 1); # 30
sub prefix:<Σ>( *@number-list ) is export {
[+] @number-list
}
```

Then execute `perl6 tools/make-docs.p6` to generate documentation
pages with your symbols included.

If you want to add a tutorial, write it as Markdown and add to `docs`
directory of this repository.

##### I want to change existing documentation

Depending on what it is, the documentation might be generated or not.

* Try to run a search in the repository for a line you want to change, for example, `grep -R "bad typo" .`
* If you see more than two files, try to narrow your search pattern
* If you see two files found, most likely one will be corresponding to sources or generated Markdown documentation. Please, patch the documentation in sources and, after re-generating pages with `tools/make-docs.p6` script, send a PR.
* If you see a single file found, Markdown file with a tutorial or this introduction text, please, patch it and send a PR.
* When not sure, please, create a ticket at [Red bugtracker](https://github.com/FCO/Red/issues)

All pull requests are welcome!
67 changes: 0 additions & 67 deletions docs/introduction.md

This file was deleted.

3 changes: 2 additions & 1 deletion lib/MetamodelX/Red/Comparate.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use Red::Attr::Column;
unit role MetamodelX::Red::Comparate;
has SetHash $!added-method .= new;

#| Creates methods to return columns
#| An internal method that generates Red getters and setters for an
#| attribute $attr of a type.
method add-comparate-methods(Mu:U \type, Red::Attr::Column $attr --> Empty) {
unless $!added-method{"{ type.^name }|$attr"} {
if $attr.rw {
Expand Down
9 changes: 5 additions & 4 deletions lib/MetamodelX/Red/Describable.pm6
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,24 @@ method !create-column($_ --> Red::Cli::Column) {
|(:references(%(table => .ref.attr.package.^table, column => .ref.name)) if .references)
}

#| Return a `Red::Cli::Table` describing the table
#| Returns an object of type `Red::Cli::Table` that represents
#| a database table of the caller.
method describe(\model --> Red::Cli::Table) {
Red::Cli::Table.new: :name(self.table(model)), :model-name(self.name(model)),
:columns(self.columns>>.column.map({self!create-column($_)}).cache)
}

#| Returns the difference to transform this model to the database version
#| Returns the difference to transform this model to the database version.
method diff-to-db(\model --> Red::Cli::Table) {
model.^describe.diff: $*RED-DB.schema-reader.table-definition: model.^table
}

#| Returns the difference to transform the DB table into this model
#| Returns the difference to transform the DB table into this model.
method diff-from-db(\model --> Red::Cli::Table) {
$*RED-DB.schema-reader.table-definition(model.^table).diff: model.^describe
}

#| Returns the difference between two models
#| Returns the difference between two models.
method diff(\model, \other-model --> Red::Cli::Table) {
model.^describe.diff: other-model.^describe
}
Loading

0 comments on commit 5ea5d85

Please sign in to comment.