Skip to content

Repository files navigation

Build StatusSparrowCI

Red

Take a look at our Documentation: https://fco.github.io/Red/

Red - A WiP ORM for Raku

INSTALL

Install with (you need rakudo 2018.12-94-g495ac7c00 or newer):

zef install Red

If the command above doesn't work, you may need to add the following flag to the install command:

zef install --exclude="pq:ver<5>:from<native>" Red

The above --exclude flag tells zef to not try and install the dependency pq directly as it is a native library (it's referencing libpq.)

SYNOPSIS

use Red:api<2>;
model Person {...}
model Post isrw {
hasInt$.idis serial;
hasInt$!author-idis referencing( *.id, :model(Person) );
hasStr$.titleis column{ :unique };
hasStr$.bodyis column;
has Person $.authoris relationship{ .author-id };
hasBool$.deletedis column =False;
hasDateTime$.createdis column .= now;
hasSet$.tagsis column{
:type<string>,
:deflate{ .keys.join:"," },
:inflate{ set(.split:",") }
} = set();
methoddelete { $!deleted=True; self.^save }
}
model Person isrw {
hasInt$.idis serial;
hasStr$.nameis column;
has Post @.postsis relationship{ .author-id };
methodactive-posts { @!posts.grep:not*.deleted }
}
my$*RED-DB= database "SQLite";
Person.^create-table;
-- Equivalent to the following query:CREATETABLEperson(
id integerNOT NULLprimary key
AUTOINCREMENT,
name varchar(255) NOT NULL
)
Post.^create-table;
-- Equivalent to the following query:CREATETABLEpost(
id integerNOT NULLprimary key AUTOINCREMENT,
author_id integerNULLreferences person(id),
title varchar(255) NOT NULL,
body varchar(255) NOT NULL,
deleted integerNOT NULL,
created varchar(32) NOT NULL,
tags varchar(255) NOT NULL,
UNIQUE (title)
)
my Post $post1= Post.^load::42id;
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHEREpost.id=42
my Post $post1= Post.^load:42;
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHEREpost.id=42
my Post $post1= Post.^load::title("my title");
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHEREpost.title= ‘my title’
my$person= Person.^create::name<Fernando>;
-- Equivalent to the following query:INSERT INTO person(
name
)
VALUES(
$1
) RETURNING *-- BIND: ["Fernando"]
RETURNS:
Person.new(name =>"Fernando")
say$person.posts;
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHEREpost.author_id= ?
-- BIND: [1]
say Person.new(:2id)
.active-posts
.grep: { .created > now }
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHERE
(
post.author_id= ?
AND (
post.deleted==0ORpost.deleted IS NULL
)
)
ANDpost.created>1554246698.448671-- BIND: [2]
my$now= now;
say Person.new(:3id)
.active-posts
.grep: { .created >$now }
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHERE
(
post.author_id= ?
AND (
post.deleted==0ORpost.deleted IS NULL
)
)
ANDpost.created> ?
-- BIND: [-- 3,-- Instant.from-posix(-- <399441421363/257>,-- Bool::False-- )-- ]
Person.^create::name<Fernando>,
:posts[
{
:title("My new post"),
:body("A long post")
},
]
;
-- Equivalent to the following query:INSERT INTO person(
name
)
VALUES(
?
) RETURNING *-- BIND: ["Fernando"]INSERT INTO post(
created,
title,
author_id,
tags,
deleted,
body
)
VALUES(
?,
?,
?,
?,
?,
?
) RETURNING *-- BIND: [-- "2019-04-02T22:55:13.658596+01:00",-- "My new post",-- 1,-- "",-- Bool::False,-- "A long post"-- ]
my$post= Post.^load::title("My new post");
-- Equivalent to the following query:SELECTpost.id,
post.author_idas"author-id",
post.title,
post.body,
post.deleted,
post.created,
post.tagsFROM
post
WHEREpost.title= ‘My new post’
-- BIND: []
RETURNS:
Post.new(
title =>"My new post",
body =>"A long post",
deleted =>0,
created =>DateTime.new(
2019,
4,
2,
23,
7,
46.677388,
:timezone(3600)
),
tags =>Set.new("")
)
say$post.body;
PRINTS:
A long post
my$author=$post.author;
RETURNS:
Person.new(name =>"Fernando")
$author.name="John Doe";
$author.^save;
-- Equivalent to the following query:UPDATE person SET
name = ‘John Doe’
WHERE id =1
$author.posts.create::title("Second post"),
:body("Another long post");
-- Equivalent to the following query:INSERT INTO post(
title,
body,
created,
tags,
deleted,
author_id
)
VALUES(
?,
?,
?,
?,
?,
?
) RETURNING *-- BIND: [-- "Second post",-- "Another long post",-- "2019-04-02T23:28:09.346442+01:00",-- "",-- Bool::False,-- 1-- ]
$author.posts.elems;
-- Equivalent to the following query:SELECTcount(*) as"data_1"FROM
post
WHEREpost.author_id= ?
-- BIND: [1]
RETURNS:2

DESCRIPTION

Red is a WiP ORM for Raku.

traits

  • is column

  • is column{}

  • is id

  • is id{}

  • is serial

  • is referencing{}

  • is relationship{}

  • is table<>

  • is nullable

features:

relationships

Red will infer relationship data if you use type constraints on your properties.

# Single file e.g. Schema.pm6
model Related { ... }
# belongs to
model MyModel {
hasInt$!related-idis referencing( *.id, :model<Related> );
has Related $.relatedis relationship{ .id };
}
# has one/has many
model Related {
hasInt$.idis serial;
has MyModel @.my-modelsis relationship{ .related-id };
}

If you want to put your schema into multiple files, you can create an "indirect" relationship, and Red will look up the related models as necessary.

# MyModel.pm6
model MyModel {
hasInt$!related-idis referencing{ :model<Related>, :column<id> };
has$.relatedis relationship({ .id }, :model<Related>);
}
# Related.pm6
model Related {
hasInt$.idis serial;
has@.my-modelsis relationship({ .related-id }, :model<MyModel>);
}

If Red can’t find where your model is defined you can override where it looks with require:

hasInt$!related-idis referencing{ :model<Related>, :column<id>,
:require<MyApp::Schema::Related> };

custom table name

model MyModel is table<custom_table_name> {}

not nullable columns by default

Red, by default, has not nullable columns, to change it:

#| This makes this model’s columns nullable by default
model MyModel is nullable {
hasInt$.col1is column; #= this column is nullablehasInt$.col2is column{ :!nullable }; #= this one is not nullable
}

load object from database

MyModel.^load:42;
MyModel.^load:id =>42;

save object on the database

$object.^save;

search for a list of object

Question.^all.grep: { .answer ==42 }; # returns a result seq

phasers

  • before-create

  • after-create

  • before-update

  • after-update

  • before-delete

  • after-delete

Temporary table

model Bla istemp { ... }

Create table

Question.^create-table;
Question.^create-table::if-not-exists;
Question.^create-table::unless-exists;

IN

Question.^all.grep:*.answer  (3.14, 13, 42)

create

Post.^create::body("bla ble bli blo blu"), :title("qwer");
model Tree {
hasUInt$!idis id;
hasStr$.valueis column;
hasUInt$!parent-idis referencing{ Tree.id };
has Tree $.parentis relationship{ .parent-id };
has Tree @.kidsis relationship{ .parent-id };
}
Tree.^create-table::if-not-exists;
Tree.^create::value<Bla>,
:parent{:value<Ble>},
:kids[
{:value<Bli>},
{:value<Blo>},
{:value<Blu>}
]
;

AUTHOR

Fernando Correa de Oliveira fernandocorrea@gmail.com

COPYRIGHT AND LICENSE

Copyright 2018 Fernando Correa de Oliveira

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

About

A WiP ORM for Raku

Topics

Resources

Contributing

Stars

77 stars

Watchers

9 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages