Red.git | lib/Red/ | Column.rakumod
use Red::Utils;
use Red::Model;
use Red::AST;
use Red::AST::Unary;
use Red::AST::IsDefined;
use Red::Formatter;
=head2 Red::Column
#| Represents a database column
unit class Red::Column does Red::AST;
also does Red::Formatter;
sub inflator($attr) {
do with $attr.type.?inflator {
$_
} else {
do if $attr.type ~~ Enumeration {
-> $value, $type { $type.($value) }
} else {
-> $_ { .?"{ $attr.type.^name }"() // .self }
}
}
}
sub deflator($_) {
do with .type.?deflator {
$_
} else {
do if .type ~~ Enumeration {
*.value
} else {
*.self
}
}
}
has Attribute $.attr is required;
has $.model = $!attr.package;
has Str $.attr-name = $!attr.name.substr: 2;
has Bool $.id = False;
has Bool $.auto-increment = False;
has &.references;
has &!actual-references;
has $!ref;
has Bool $.nullable = $!attr.package.HOW.?default-nullable($!attr.package) // False;
has Str $.name = ::?CLASS.column-formatter: self.attr.name.substr: 2;
has Str $.name-alias = $!name;
has Str $.type;
has &.inflate = $!attr.&inflator;
has &.deflate = $!attr.&deflator;
has $.computation;
has Str $.model-name;
has Red::Model $.model-type;
has Str $.column-name;
has Str $.require = $!model-name;
has Mu $.class = $!attr.package;
has @.unique-groups;
has &.default;
#multi method WHICH(::?CLASS:D:) {
# ValueObjAt.new: self.^name ~ "|" ~ self.migration-hash.pairs.sort.map(-> (:$key, :$value) {
# "$key|$value"
# }).join: "|"
#}
multi method WHICH(::?CLASS:D:) {
ValueObjAt.new: self.gist
}
multi method raku(::?CLASS:D:) {
"{ self.^name }.new({
self.Hash.pairs.sort.map(-> (:$key, :$value) {
next if $key eq <inflate deflate>.one;
"$key.Str() => $value.raku()"
}).join: ", "
})"
}
method Hash(--> Hash()) {
%(
|(:attr($_) with $!attr ),
|(:attr-name($_) with $!attr-name ),
|(:id($_) with $!id ),
|(:auto-increment($_) with $!auto-increment ),
|(:references($_) with &!references ),
|(:actual-references($_) with &!actual-references),
|(:ref($_) with $!ref ),
|(:nullable($_) with $!nullable ),
|(:name($_) with $!name ),
|(:name-alias($_) with $!name-alias ),
|(:type($_) with $!type ),
|(:inflate($_) with &!inflate ),
|(:deflate($_) with &!deflate ),
|(:computation($_) with $!computation ),
|(:model-name($_) with $!model-name ),
|(:column-name($_) with $!column-name ),
|(:require($_) with $!require ),
)
}
#| Returns a Hash that represents the column for migration purposes
method migration-hash(--> Hash()) {
|(:name($_) with $!name ),
|(:type(.type.^name) with $!attr ),
|(:references-table(.attr.package.^table) with $.ref ),
|(:references-column(.column-name) with $.ref ),
|(:is-id($_) with $!id ),
|(:is-auto-increment($_) with $!auto-increment ),
|(:is-nullable($_) with $!nullable ),
}
#| Subclass used to lazy evaluation of parameter types
class ReferencesProxy does Callable {
has Str $.model-name is required;
has Str $.column-name;
has Str $.require = $!model-name;
has $.model;
has &.references;
has Bool $!tried-model = False;
method model($alias = Nil --> Mu:U) {
if !$!tried-model {
my $model = ::($!model-name);
if !$model && $model ~~ Failure {
require ::($!require);
$model = ::($!model-name);
}
$!model = $model;
$!tried-model = True;
}
do if $alias !=== Nil {
do if $alias.^table eq $!model.^table {
$alias
} else {
die "$alias.^name() isn't an alias for the table $!model.^table()"
}
} else {
$!model
}
}
method CALL-ME($alias = Nil) {
my $*RED-INTERNAL = True;
if &!references {
my $model = self.model($alias);
my $ret = &!references.($model);
if $ret ~~ Red::Column && $ret.class.^name eq '$?CLASS' {
$ret .= clone: :class($model)
}
$ret
}
else {
self.model($alias).^columns.first(*.column.attr-name eq $!column-name).column
}
}
}
#| Returns the class that column is part of.
#method class { self.attr.package }
#| Method that returns the comment for the column
method comment { .Str with self.attr.WHY }
#| Returns a function that will return a column that is referenced by this column
method references(--> Callable) is rw {
&!actual-references //= do {
if &!references {
if $!model-name {
ReferencesProxy.new(:&!references, :$!model-name, :$!require, :$!model-type);
}
else {
&!references;
}
}
elsif $!model-name && $!column-name {
ReferencesProxy.new(:$!model-name, :$!column-name, :$!require, :$!model-type);
}
else {
Callable
}
}
}
#| Returns the column that is referenced by this one.
method ref($model = $!model-type !=== Red::Model ?? $!model-type !! Nil) {
my $*RED-INTERNAL = True;
.($model) with self.references
}
#| Required by the Red::AST role
method returns { $!attr.type }
method transpose(&func) { func self }
method gist { "{$!attr.package.HOW.^can("as") ?? $!attr.package.^as !! "({ $!attr.package.^name })"}.{$!name-alias}" }
method cast(Str $type) {
Red::AST::Cast.new: self, $type
}
method find-column-name {
$!attr-name
}
#| Returns an alias of that column
method alias(Str $name) {
self.clone: name-alias => $name
}
#| Returns a clone using a different name
method as(Str $name, :$nullable = True) {
self.clone: attr-name => $name, :$name, id => False, :$nullable, attr => Attribute
}
submethod TWEAK(:$unique) {
if $!attr.type.^find_method: "red-type-db-methods" {
self does $!attr.type.red-type-db-methods
}
with $unique {
when Bool {
$!attr.package.^add-unique-constraint: { self }
}
when Positional {
self.unique-groups.append: |$unique
}
default {
self.unique-groups.push: $unique
}
}
}
#| Do not test definedness, but returns a new Red::AST::IsDefined.
#| It's used to test `IS NULL` on the given column. It's also used
#| by any construction that naturally uses `.defined`.
method defined {
Red::AST::IsDefined.new: self
}
method args {}
method not { Red::AST::Not.new: self }