Help language development. Donate to The Perl Foundation
Take a look at our Documentation: https://fco.github.io/Red/
Install with (you need rakudo 2018.12-94-g495ac7c00 or newer):
zef install Red
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
Red is a WiP ORM for Raku.
is column
is column{}
is id
is id{}
is serial
is referencing{}
is relationship{}
is table<>
is nullable
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> };
model MyModel is table<custom_table_name> {}
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 }
MyModel.^load: 42; MyModel.^load: id => 42;
$object.^save;
Question.^all.grep: { .answer == 42 }; # returns a result seq
before-create
after-create
before-update
after-update
before-delete
after-delete
model Bla is temp { ... }
Question.^create-table; Question.^create-table: :if-not-exists; Question.^create-table: :unless-exists;
Question.^all.grep: *.answer ⊂ (3.14, 13, 42)
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>} ] ;
Fernando Correa de Oliveira [email protected]
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.