Red

A Raku ORM

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

SYNOPSIS

use Red:api<2>;

model Person {...}

model Post is rw {
    has Int         $.id        is serial;
    has Int         $!author-id is referencing( *.id, :model(Person) );
    has Str         $.title     is column{ :unique };
    has Str         $.body      is column;
    has Person      $.author    is relationship{ .author-id };
    has Bool        $.deleted   is column = False;
    has DateTime    $.created   is column .= now;
    has Set         $.tags      is column{
        :type<string>,
        :deflate{ .keys.join: "," },
        :inflate{ set(.split: ",") }
    } = set();
    method delete { $!deleted = True; self.^save }
}

model Person is rw {
    has Int  $.id            is serial;
    has Str  $.name          is column;
    has Post @.posts         is relationship{ .author-id };
    method active-posts { @!posts.grep: not *.deleted }
}

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

Person.^create-table;
-- Equivalent to the following query:
CREATE TABLE person(
    id integer NOT NULL primary key
    AUTOINCREMENT,
    name varchar(255) NOT NULL
)
Post.^create-table;
-- Equivalent to the following query:
CREATE TABLE post(
    id integer NOT NULL primary key AUTOINCREMENT,
    author_id integer NULL references person(id),
    title varchar(255) NOT NULL,
    body varchar(255) NOT NULL,
    deleted integer NOT NULL,
    created varchar(32) NOT NULL,
    tags varchar(255) NOT NULL,
    UNIQUE (title)
)
my Post $post1 = Post.^load: :42id;
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    post.id = 42
my Post $post1 = Post.^load: 42;
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    post.id = 42
my Post $post1 = Post.^load: :title("my title");
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    post.title = ‘my title’
my $person = Person.^create: :name<Fernando>;
-- Equivalent to the following query:
INSERT INTO person(
    name
)
VALUES(
    ?
)
-- BIND: ["Fernando"]

-- SQLite needs an extra select:

SELECT
    person.id,
    person.name
FROM
    person
WHERE
    _rowid_ = last_insert_rowid()
LIMIT 1
RETURNS:
Person.new(name => "Fernando")
# Using Pg Driver for this block
{
    my $*RED-DB = database "Pg";
    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:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    post.author_id = ?
-- BIND: [1]
say Person.new(:2id)
    .active-posts
    .grep: { .created > now }
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    (
       post.author_id = ?
       AND (
           post.deleted == 0
           OR post.deleted IS NULL
       )
    )
    AND post.created > 1554246698.448671
-- BIND: [2]
my $now = now;
say Person.new(:3id)
    .active-posts
    .grep: { .created > $now }
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    (
       post.author_id = ?
       AND (
           post.deleted == 0
           OR post.deleted IS NULL
       )
    )
    AND post.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(
    ?
)
-- BIND: ["Fernando"]

SELECT
    person.id,
    person.name
FROM
    person
WHERE
    _rowid_ = last_insert_rowid()
LIMIT 1
-- BIND: []

INSERT INTO post(
    created,
    title,
    author_id,
    tags,
    deleted,
    body
)
VALUES(
    ?,
    ?,
    ?,
    ?,
    ?,
    ?
)
-- BIND: [
--   "2019-04-02T22:55:13.658596+01:00",
--   "My new post",
--   1,
--   "",
--   Bool::False,
--   "A long post"
-- ]

SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    _rowid_ = last_insert_rowid()
LIMIT 1
my $post = Post.^load: :title("My new post");
-- Equivalent to the following query:
SELECT
    post.id,
    post.author_id as "author-id",
    post.title,
    post.body,
    post.deleted,
    post.created,
    post.tags
FROM
    post
WHERE
    post.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(
    ?,
    ?,
    ?,
    ?,
    ?,
    ?
)
-- 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:
SELECT
    count(*) as "data_1"
FROM
    post
WHERE
    post.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 {
    has Int     $!related-id is referencing( *.id, :model<Related> );
    has Related $.related    is relationship{ .id };
}

# has one/has many
model Related {
    has Int $.id is serial;
    has MyModel @.my-models is 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 {
    has Int     $!related-id is referencing{ :model<Related>, :column<id> };
    has         $.related    is relationship({ .id }, :model<Related>);
}

# Related.pm6
model Related {
    has Int $.id is serial;
    has     @.my-models is relationship({ .related-id }, :model<MyModel>);
}

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

    has Int     $!related-id is 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 {
    has Int $.col1 is column;               #= this column is nullable
    has Int $.col2 is 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 is temp { ... }

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 {
    has UInt   $!id        is id;
    has Str    $.value     is column;
    has UInt   $!parent-id is referencing{ Tree.id };

    has Tree   $.parent    is relationship{ .parent-id };
    has Tree   @.kids      is 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 [email protected]

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.

Red v0.1.55

A Raku ORM

Authors

  • Fernando Correa de Oliveira

License

Artistic-2.0

Dependencies

DBIishDB::PgUUID

Test Dependencies

Provides

  • CX::Red::Bool
  • MetamodelX::Red::Comparate
  • MetamodelX::Red::Describable
  • MetamodelX::Red::Dirtable
  • MetamodelX::Red::Id
  • MetamodelX::Red::Migration
  • MetamodelX::Red::Model
  • MetamodelX::Red::OnDB
  • MetamodelX::Red::Populatable
  • MetamodelX::Red::Refreshable
  • MetamodelX::Red::Relationship
  • MetamodelX::Red::Specialisable
  • MetamodelX::Red::Supply
  • Red
  • Red::AST
  • Red::AST::AddForeignKeyOnTable
  • Red::AST::BeginTransaction
  • Red::AST::Case
  • Red::AST::Chained
  • Red::AST::ChangeColumn
  • Red::AST::Comment
  • Red::AST::CommitTransaction
  • Red::AST::Constraints
  • Red::AST::CreateColumn
  • Red::AST::CreateTable
  • Red::AST::DateTimeFuncs
  • Red::AST::Delete
  • Red::AST::Divisable
  • Red::AST::DropColumn
  • Red::AST::Empty
  • Red::AST::Function
  • Red::AST::Generic::Infix
  • Red::AST::Generic::Postfix
  • Red::AST::Generic::Prefix
  • Red::AST::Infix
  • Red::AST::Infixes
  • Red::AST::Insert
  • Red::AST::Intersect
  • Red::AST::IsDefined
  • Red::AST::JsonItem
  • Red::AST::JsonRemoveItem
  • Red::AST::LastInsertedRow
  • Red::AST::Minus
  • Red::AST::MultiSelect
  • Red::AST::Next
  • Red::AST::Operator
  • Red::AST::Optimizer::AND
  • Red::AST::Optimizer::Case
  • Red::AST::Optimizer::OR
  • Red::AST::RollbackTransaction
  • Red::AST::Select
  • Red::AST::StringFuncs
  • Red::AST::TableComment
  • Red::AST::Unary
  • Red::AST::Union
  • Red::AST::Update
  • Red::AST::Value
  • Red::Attr::Column
  • Red::Attr::Query
  • Red::Attr::Relationship
  • Red::Class
  • Red::Cli
  • Red::Cli::Column
  • Red::Cli::Relationship
  • Red::Cli::Table
  • Red::Column
  • Red::ColumnMethods
  • Red::Config
  • Red::DB
  • Red::Database
  • Red::DefaultResultSeq
  • Red::Do
  • Red::Driver
  • Red::Driver::Cache
  • Red::Driver::Cache::Memory
  • Red::Driver::CacheInvalidateOnWrite
  • Red::Driver::CacheWithStrKey
  • Red::Driver::CommonSQL
  • Red::Driver::Mock
  • Red::Driver::Pg
  • Red::Driver::SQLite
  • Red::Driver::SQLite::SQLiteMaster
  • Red::Driver::SQLite::SchemaReader
  • Red::Event
  • Red::Formatter
  • Red::FromRelationship
  • Red::HiddenFromSQLCommenting
  • Red::Migration::Column
  • Red::Migration::Migration
  • Red::Migration::Table
  • Red::Model
  • Red::Operators
  • Red::Phaser
  • Red::PrepareCode
  • Red::ResultAssociative
  • Red::ResultSeq
  • Red::ResultSeq::Iterator
  • Red::ResultSeqSeq
  • Red::Schema
  • Red::SchemaReader
  • Red::Statement
  • Red::Traits
  • Red::Type
  • Red::Type::Json
  • Red::Utils
  • X::Red::Exceptions

Documentation

The Camelia image is copyright 2009 by Larry Wall. "Raku" is trademark of the Yet Another Society. All rights reserved.