RaCoCo result: MetamodelX::Red::Model

# MetamodelX::Red::Model 78%

    use v6;
    use Red::Model;
    use Red::Attr::Column;
    use Red::Column;
    use Red::Utils;
    use Red::ResultSeq;
    use Red::DefaultResultSeq;
    use Red::Attr::Query;
    use Red::DB;
    use Red::AST;
    use Red::AST::Value;
    use Red::AST::Insert;
    use Red::AST::Delete;
    use Red::AST::Update;
    use Red::AST::Infixes;
    use Red::AST::CreateTable;
    use Red::AST::Constraints;
    use Red::AST::TableComment;
    use Red::AST::LastInsertedRow;
    use MetamodelX::Red::Dirtable;
    use MetamodelX::Red::Comparate;
    use MetamodelX::Red::Migration;
    use MetamodelX::Red::Relationship;
    use MetamodelX::Red::Describable;
    use MetamodelX::Red::OnDB;
    use MetamodelX::Red::Id;
    use MetamodelX::Red::Populatable;
    use MetamodelX::Red::Specialisable;
    use Red::Formatter;
    use X::Red::Exceptions;
    use Red::Phaser;
    use Red::Event;
    use Red::PrepareCode;

    =head2 MetamodelX::Red::Model

    unit class MetamodelX::Red::Model is Metamodel::ClassHOW;
    also does MetamodelX::Red::Dirtable;
    also does MetamodelX::Red::Comparate;
    #also does MetamodelX::Red::Migration;
    also does MetamodelX::Red::Relationship;
    also does MetamodelX::Red::Describable;
    also does MetamodelX::Red::OnDB;
    also does MetamodelX::Red::Id;
    also does MetamodelX::Red::Populatable;
    also does MetamodelX::Red::Specialisable;
    also does Red::Formatter;

    has Attribute @!columns;
    has Red::Column %!references;
    has %!attr-to-column;
    has $.rs-class;
    has @!constraints;
    has $.table;
    has Bool $!temporary;
    has Bool $!default-null;
    has %!alias-cache;
    has $!alias-cache-lock = Lock.new;

    multi method emit(Mu $model, Red::Event $event) {
        start try get-RED-DB.emit: $event.clone: :model($model.WHAT)
    }

    multi method emit(Mu $model, $data, Exception :$error, Red::Model :$origin) {
        self.emit: $model, Red::Event.new: :model($model.WHAT), |(:$data with $data), |(:$error with $error)
    }

    #| Returns a list of columns names.of the model.
    method column-names(|) { @!columns>>.column>>.name }

    #| Returns a hash of model constraints classified by type.
    method constraints(|) { @!constraints.unique.classify: *.key, :as{ .value } }

    #| Returns a hash of foreign keys of the model.
    method references(|) { %!references }

    #| Returns the table name for the model.
    method table(Mu \type) is rw {
        $!table //= self.table-formatter: type.HOW.?experimental-name(type) // type.^name
    }

    #| Returns the table alias
    method as(Mu \type) { self.table: type }

    #| Returns the original model
    method orig(Mu \type) { type.WHAT }

    #| Returns the name of the ResultSeq class
    method rs-class-name(Mu \type) { "{type.^name}::ResultSeq" }

    #| Returns a list of columns
    method columns(|) is rw {
        @!columns
    }

    #| Returns a hash with the migration hash
    method migration-hash(\model --> Hash()) {
        columns => @!columns>>.column>>.migration-hash,
        name    => model.^table,
        version => model.^ver // v0,
    }

    #| Returns a liast of id values
    method id-values(Red::Model:D $model) {
        self.id($model).map({ .get_value: $model }).list
    }

    #| Check if the model is nullable by default.
    method default-nullable(|) is rw { $!default-null }

    #| Returns all columns with the unique counstraint
    method unique-constraints(\model) {
        @!constraints.unique.grep(*.key eq "unique").unique.map: *.value.map: *.attr
    }

    #| A map from attr to column
    method attr-to-column(|) is rw {
        %!attr-to-column
    }

    method set-helper-attrs(Mu \type) {
        self.MetamodelX::Red::Dirtable::set-helper-attrs(type);
        self.MetamodelX::Red::OnDB::set-helper-attrs(type);
        self.MetamodelX::Red::Id::set-helper-attrs(type);
    }

    method new(|c) {
        my @eroles = @Red::experimental-roles.grep(self !~~ *).sort({ $^a.^name cmp $^b.^name});
        if +@eroles {
            return (self.^mixin: |@eroles).new(|c)
        }
        nextsame
    }

    #| Compose
    method compose(Mu \type) {

        self.set-helper-attrs: type;

        type.^prepare-relationships;

        if $.rs-class === Any {
            my $rs-class-name = $.rs-class-name(type);
            # TODO
            #my $rs-class = ::($rs-class-name);
            #if !$rs-class && $rs-class !~~ Failure  {
            #    $!rs-class = $rs-class;
            #} else {
                $!rs-class := create-resultseq($rs-class-name, type);
                type.WHO<ResultSeq> := $!rs-class
            #}
        }
        die "{$.rs-class.^name} should do the Red::ResultSeq role" unless $.rs-class ~~ Red::ResultSeq;
        self.add_role: type, Red::Model;
        self.add_role: type, role :: {
            method TWEAK(|c) {
                self.^set-dirty: self.^columns;
                self.?TWEAK-MODEL(|c)
            }
        }
        my @roles-cols = self.roles_to_compose(type).grep({ .HOW !~~ Metamodel::CurriedRoleHOW }).flatmap(*.^attributes).grep: Red::Attr::Column;
        for @roles-cols -> Red::Attr::Column $attr {
            self.add-comparate-methods: type, $attr
        }

        type.^compose-columns;
        self.Metamodel::ClassHOW::compose(type);
        type.^compose-columns;

        for type.^attributes -> $attr {
            %!attr-to-column{$attr.name} = $attr.column.name if $attr ~~ Red::Attr::Column:D;
        }

        self.compose-dirtable: type;

        for type.^columns.grep(*.column.unique-groups.elems > 0).categorize(*.column.unique-groups).values -> @grp {
            type.^add-unique-constraint: -> | { @grp>>.column }
        }

        if type.^constraints<pk>:!exists {
            type.^add-pk-constraint: type.^id>>.column if type.^id > 1
        }
    }

    #| Creates a new reference (foreign key).
    method add-reference($name, Red::Column $col) {
        %!references{$name} = $col
    }

    #| Creates a new unique constraint.
    method add-unique-constraint(Mu:U \type, &columns) {
        my $*RED-INTERNAL = True;
        @!constraints.push: "unique" => columns(type)
    }

    #| Creates a new primary key constraint.
    multi method add-pk-constraint(Mu:U \type, &columns) {
        my $*RED-INTERNAL = True;
        nextwith type, columns(type)
    }

    #| Creates the primary key constraint.
    multi method add-pk-constraint(Mu:U \type, @columns) {
        @!constraints.push: "pk" => @columns
    }

    method tables(\model) { [ model ] }

    proto method join($, $, $, :$name, *%pars where { .elems == 0 || ( .elems == 1 && get-RED-DB.join-type(.keys.head) && so .values.head ) }) {*}

    multi method join(\model, \to-join, &on, :$name, *%pars) {
        to-join.^alias: |($_ with $name), :base(model), :relationship(&on.assuming: model), :join-type(%pars.keys.head // "")
    }

    multi method join(\model, \to-join, Red::AST $on, :$name, *%pars) {
        to-join.^alias: |($_ with $name), :base(model), :relationship($on), :join-type(%pars.keys.head // "")
    }

    multi method join(\model, \to-join, $on where *.^can("relationship-ast"), :$name, *%pars) {
        to-join.^alias: |($_ with $name), :base(model), :relationship($on), :join-type(%pars.keys.head // "")
    }

    my UInt $alias_num = 1;
    method alias(|c (Red::Model:U \type, Str $name = "{type.^name}_{$alias_num++}", :$base, :$relationship, :$join-type)) {
        return %!alias-cache{$name} if %!alias-cache{$name}:exists;
        my \alias = ::?CLASS.new_type(:$name);

        $!alias-cache-lock.protect: {
            my role RAlias[Red::Model:U \rtype, Str $rname, \alias, \rel, \base, \join-type, @cols] {
                method columns(|)     { @cols }
                method table(|)       { rtype.^table }
                method as(|)          { self.table-formatter: $rname }
                method orig(|)        { rtype }
                method join-type(|)   { join-type }
                method tables(|)      { [ |base.^tables, alias ] }
                method join-on(|)     {
                    my $*RED-INTERNAL = True;
                    do given rel {
                        when Red::AST {
                            $_
                        }
                        when Callable {
                            my $filter = do given what-does-it-do($_, alias) {
                                do if [eqv] .values {
                                    .values.head
                                } else {
                                    .kv.map(-> $test, $ret {
                                        do with $test {
                                            Red::AST::AND.new: $test, ast-value $ret
                                        } else {
                                            $ret
                                        }
                                    }).reduce: { Red::AST::OR.new: $^agg, $^fil }
                                }
                            }
                            with $*RED-GREP-FILTER {
                                $filter = Red::AST::AND.new: ($_ ~~ Red::AST ?? $_ !! .&ast-value), $filter
                            }
                            $filter
                        }
                        default {
                            .relationship-ast(alias)

                        }
                    }
                }
            }
        #    alias.^add_role: Red::Model;
            my @cols = do for @!columns -> $col {
                my $new-col = Attribute.new:
                    :name($col.name),
                    :package(alias),
                    :type($col.type),
                    :has_acessor($col.has_accessor),
                    :build($col.build)
                ;
                $new-col does Red::Attr::Column($col.column.Hash);
                alias.^add-comparate-methods: $new-col;
                $new-col
            }
            alias.HOW does RAlias[type, $name, alias, $relationship, $base, $join-type, @cols];
            for self.relationships.keys -> $rel {
                alias.^add-relationship: $rel.transfer: alias
            }
            alias.^compose;
            %!alias-cache{$name} = alias;
        }
        alias
    }

    #| Creates a new column and adds it to the model.
    method add-column(::T Red::Model:U \type, Red::Attr::Column $attr) {
        if $attr.name eq @!columns.none.name {
            @!columns.push: $attr;
            my $name = $attr.name.substr: 2;
            with $attr.args{"references" | "model-name"} {
                self.add-reference: $name, $attr.column
            }
            self.add-comparate-methods(T, $attr);
            if $attr.has_accessor && !T.^can($name) {
                if type.^rw or $attr.rw {
                    $attr does role :: { method rw { True } };
                    T.^add_multi_method: $name, my method (Red::Model:D:) is rw {
                        use nqp;
                        nqp::getattr(self, self.WHAT, $attr.name)
                    }
                } else {
                    T.^add_multi_method: $name, my method (Red::Model:D:) {
                        $attr.get_value: self
                    }
                }
            }
        }
    }

    method compose-columns(Red::Model:U \type) {
        for self.attributes(type).grep: Red::Attr::Column -> Red::Attr::Column $attr {
            #        $attr.clone;
            #        $attr.create-column;
            type.^add-column: $attr.clone: :package(type)
        }
    }

    #| Returns the ResultSeq
    multi method rs(Mu:U --> Red::ResultSeq)               { $.rs-class.new }
    multi method rs(Mu:U, :$with! --> Red::ResultSeq)      { $.rs-class.new.with: $with }
    multi method rs(Mu:D $obj --> Red::ResultSeq)          { $.rs-class.new: :$obj }
    multi method rs(Mu:D $obj, :$with! --> Red::ResultSeq) { $.rs-class.new(:$obj).with($with) }

    #| Alias for C<.rs()>
    multi method all($obj --> Red::ResultSeq)          { $obj.^rs }
    multi method all($obj, :$with! --> Red::ResultSeq) { $obj.^rs(:$with) }

    #| Sets model as a temporary table
    method temp(|) is rw { $!temporary }

    multi method create-table(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.create-table: |c
    }

    multi method create-table(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.create-table: |c
    }

    #| Creates table unless table already exists
    multi method create-table(\model, Bool :unless-exists(:$if-not-exists) where so *, *%pars) {
        CATCH {
            when X::Red::Driver::Mapped::TableExists {
                return False
            }
        }
        self.create-table: model, |%pars
    }

    #| Creates table
    multi method create-table(\model, :$with where not .defined, :if-not-exists($unless-exists) where not .defined) {
        die X::Red::InvalidTableName.new: :table(model.^table)
            unless get-RED-DB.is-valid-table-name: model.^table
        ;
        my $data = Red::AST::CreateTable.new:
                :name(model.^table),
                :temp(model.^temp),
                :columns(model.^columns.map(*.column.clone: :class(model))),
                :constraints[
                    |@!constraints.unique.map: {
                        when .key ~~ "unique" {
                            Red::AST::Unique.new: :columns[|.value]
                        }
                        when .key ~~ "pk" {
                            Red::AST::Pk.new: :columns[|.value]
                        }
                    }
                ],
                |(:comment(Red::AST::TableComment.new: :msg(.Str), :table(model.^table)) with model.WHY)
        ;
        get-RED-DB.execute: |$data;
        self.emit: model, $data;
        CATCH {
            default {
                self.emit: model, $data, :error($_);
                proceed
            }
        }
        True
    }

    #| Applies phasers
    method apply-row-phasers($obj, Mu:U $phase ) {
        my $*RED-INTERNAL = True;
        for (|$obj.^methods.grep($phase), |$obj.^private_method_table.values.grep($phase)) -> $meth {
            $obj.$meth(|($meth.count > 1 ?? $obj !! Empty));
        }
    }

    multi method save(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.save: |c
    }

    multi method save(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.save: |c
    }

    #| Saves that object on database (create a new row)
    multi method save($obj, Bool :$insert! where * == True, Bool :$from-create, :$with where not .defined) {
        self.apply-row-phasers($obj, BeforeCreate) unless $from-create;
        my $ast = Red::AST::Insert.new: $obj;
        my $ret := get-RED-DB.execute: $ast;
        $obj.^saved-on-db;
        $obj.^populate-ids;
        self.apply-row-phasers($obj, AfterCreate) unless $from-create;
        self.emit: $obj, $ast;
        CATCH {
            default {
                self.emit: $obj, $ast, :error($_);
                proceed
            }
        }
        $obj.^clean-up;
        $ret
    }

    #| Saves that object on database (update the row)
    multi method save($obj, Bool :$update! where * == True, :$with where not .defined) {
        self.apply-row-phasers($obj, BeforeUpdate);
        my $ast = Red::AST::Update.new: $obj;
        my $ret := get-RED-DB.execute: $ast;
        $obj.^saved-on-db;
        $obj.^populate-ids;
        self.apply-row-phasers($obj, AfterUpdate);
        self.emit: $obj, $ast;
        CATCH {
            default {
                self.emit: $obj, $ast, :error($_);
                proceed
            }
        }
        $obj.^clean-up;
        $ret
    }

    #| Generic save, calls C<.^save: :insert> if C<.^is-on-db> or C<.^save: :update> otherwise
    multi method save($obj, :$with where not .defined) {
        do if $obj.^is-on-db {
            self.save: $obj, :update
        } else {
            self.save: $obj, :insert
        }
    }

    multi method create($, Red::Driver :$with!, |c) is hidden-from-backtrace {
        my $*RED-DB = $with;
        self.create: |c
    }

    multi method create($, Str :$with!, |c) is hidden-from-backtrace {
        self.create: :with(%GLOBAL::RED-DEFAULT-DRIVERS{$with}), |c
    }

    #| Creates a new object and saves it on DB
    #| It accepts a list os pairs (the same as C<.new>)
    #| And Lists and/or Hashes for relationships
    multi method create(\model where *.DEFINITE, *%orig-pars, :$with where not .defined) is hidden-from-backtrace is rw {
        my $RED-DB = get-RED-DB;
        my $trans  = so $*RED-TRANSCTION-RUNNING;
        $RED-DB   .= begin        unless $trans;
        KEEP $RED-DB.commit       unless $trans;
        UNDO try $RED-DB.rollback unless $trans;
        {
            my $*RED-DB = $RED-DB;
            my $*RED-TRANSCTION-RUNNING = True;
            my %relationships = %.relationships.keys.map: {
                .name.substr(2) => $_
            }
            my %pars;
            my %positionals;
            my %has-one{Mu};

            for %orig-pars.kv -> $name, $val {
                my \attr = model.^attributes.first(*.name.substr(2) eq $name);
                my \attr-type = attr.type;
                with %relationships{ $name } {
                    my \attr-model = attr.relationship-model;
                    if $val ~~ Positional && attr-type ~~ Positional {
                        %positionals{$name} = $val
                    } elsif .has-one {
                        die "Value of '$name' should be Associative" unless $val ~~ Associative;
    #                    my $type = attr.relationship-model;
    #                    try { attr-type.^find(|$val) } // attr-type.^create: |$val
                        %has-one{$val} = attr
                    } elsif $val ~~ Associative && $val !~~ Red::Model {
                        %pars{$name} = do if attr-model ~~ Red::Model {
                            try { attr-model.^find(|$val) } // attr-model.^create: |$val
                        } else {
                            try { attr-type.^find(|$val)  } // attr-type.^create:  |$val
                        }
                    } else {
                        %pars{$name} = $val
                    }
                } else {
                    %pars{$name} = $val
                }
            }
            my $obj = model.new: |%pars;
            self.apply-row-phasers($obj, BeforeCreate);
            my $data := $obj.^save(:insert, :from-create).row;
            my @ids = model.^id>>.column>>.attr-name;
            my @ids-col = model.^id>>.column>>.name;
            my %cols = model.^columns.map: { .column.attr-name => .column };
            my $filter = do if @ids {
                model.^id-filter: |do if $data.defined and not $data.elems {
                    $*RED-DB.execute(Red::AST::LastInsertedRow.new: model).row{|@ids}:kv
                } else {
                    do for @ids-col.kv -> $i, $k {
                        @ids[$i] => $data{$k}
                    }
                }.Hash
            } else {
                %pars.kv.map(-> $key, $value {
                    Red::AST::Eq.new: %cols{$key}, ast-value $value
                })
                    .reduce: { Red::AST::AND.new: $^a, $^b }
            } unless $data<>;

            my $no;
            if %positionals || %has-one {
                $no = do {
                    my $obj;
                    if !$data.elems {
                        $obj = model.^find: $filter
                    } else {
                        $obj = model.^new-from-data($data.elems ?? |$data !! |%orig-pars);
                        $obj.^saved-on-db;
                        $obj.^clean-up;
                        $obj.^populate-ids;
                    }
                    $obj
                }
                for %positionals.kv -> $name, @val {
                    my \rs = $no."$name"();
                    rs.create: |$_ for @val;
                }

                for %has-one.kv -> %val, \attr {
                    my $type = attr.relationship-model;
                    my $id-name = attr.rel.attr-name;
                    #TODO: What to do when there is more than one id???
                    $type.^create: |%( |%val, $id-name => $no.^id-values.head )
                }
            }
            self.apply-row-phasers($obj, AfterCreate);
            .return with $no;
            return-rw Proxy.new:
                    STORE => -> | {
                        die X::Assignment::RO.new(value => $obj)
                    },
                    FETCH => {
                        $ //= do {
                            my $obj;
                            my $*RED-DB = $RED-DB;
                            if !$data.elems {
                                $obj = model.^find: $filter
                            } else {
                                $obj = model.^new-from-data($data.elems ?? |$data !! |%orig-pars);
                                $obj.^saved-on-db;
                                $obj.^clean-up;
                                $obj.^populate-ids;
                            }
                            $obj
                        }
                    }
        }
    }

    multi method delete(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.delete: |c
    }

    multi method delete(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.delete: |c
    }

    #| Deletes row from database
    multi method delete(\model, :$with where not .defined) {
        my $origin = model.clone;
        self.apply-row-phasers(model, BeforeDelete);
        my $ast = Red::AST::Delete.new: model;
        get-RED-DB.execute: $ast;
        self.emit: model, $ast, :$origin;
        CATCH {
            default {
                self.emit: model, $ast, :$origin, :error($_);
                proceed
            }
        }
        self.apply-row-phasers(model, AfterDelete);
    }

    multi method load(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.load: |c
    }

    multi method load(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.load: |c
    }

    #| Loads object from the DB
    multi method load(Red::Model:U \model, |ids) {
        my $filter = model.^id-filter: |ids;
        model.^rs.grep({ $filter }).head
    }

    #| Creates a new object setting ids with this values
    multi method new-with-id(Red::Model:U \model, %ids, :$with where not .defined) {
        model.new: |model.^id-map: |%ids;
    }

    multi method new-with-id(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.new-with-id: |c
    }

    multi method new-with-id(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.new-with-id: |c
    }

    #| Creates a new object setting the id
    multi method new-with-id(Red::Model:U \model, |ids) {
        model.new: |model.^id-map: |ids;
    }

    multi method search(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.search: |c
    }

    multi method search(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.search: |c
    }

    #| Receives a `Block` of code and returns a `ResultSeq` using the `Block`'s return as filter
    multi method search(Red::Model:U \model, &filter, :$with where not .defined) {
        model.^rs.grep: &filter
    }

    #| Receives a `AST` of code and returns a `ResultSeq` using that `AST` as filter
    multi method search(Red::Model:U \model, Red::AST $filter, :$with where not .defined) {
        samewith model, { $filter }
    }

    #| Receives a hash of `AST`s of code and returns a `ResultSeq` using that `AST`s as filter
    #| Usualy passed unique values as IDs or columns with unique counstraint
    multi method search(Red::Model:U \model, *%filter, :$with where not .defined) {
        my $*RED-INTERNAL = True;
        samewith
            model,
            %filter.kv
                .map(-> $k, $value { Red::AST::Eq.new: model."$k"(), Red::AST::Value.new: :$value })
                .reduce: { Red::AST::AND.new: $^a, $^b }
    }

    multi method find(Red::Driver :$with!, |c) {
        my $*RED-DB = $with;
        self.find: |c
    }

    multi method find(Str :$with!, |c) {
        my $*RED-DB = %GLOBAL::RED-DEFULT-DRIVERS{$with};
        self.find: |c
    }

    #| Finds a specific row
    #| using the same data as search()
    multi method find(|c) { self.search(|c).head }

    multi method find(Red::Model:U, Any:U) { die "Could not use find without data" }

    multi method get-attr(\instance, Str $name) {
        $!col-data-attr.get_value(instance).{ $name }
    }

    multi method set-attr(\instance, Str $name, \value) {
        $!col-data-attr.get_value(instance).{ $name } = value
    }

    multi method get-attr(\instance, Red::Attr::Column $attr) {
        samewith instance, $attr.name.substr: 2
    }

    multi method set-attr(\instance, Red::Attr::Column $attr, \value) {
        samewith instance, $attr.name.substr(2), value
    }

    method new-from-data(\of, $data) is hidden-from-backtrace {
        my %cols-by-attr = of.^columns.map: { .column.attr-name => .column  }
        my %cols-by-col  = of.^columns.map: { .column.name => .column }
        my $obj = of.^orig.new: |(%($data).kv
            .map(-> $c, $v {
                do with $v {
                    unless $c.contains: "." {
                        my $col = ( %cols-by-col{$c} // %cols-by-attr{$c} );
                        die "Column '$c' not found" without $col;
                        die "Inflator not defined for column '$c'" without $col.inflate;
                        my $inflated = $col.inflate.($v, |($col.attr.type if $col.inflate.count > 1));
                        $inflated = get-RED-DB.inflate(
                                $inflated,
                                :to(of.^attributes.first(*.name.substr(2) eq $c).type)
                        ) if \(
                            get-RED-DB,
                            $inflated,
                            :to(of.^attributes.first(*.name.substr(2) eq $c).type)
                        ) ~~ get-RED-DB.^lookup("inflate").candidates.any.signature;
                        $col.attr-name => $inflated
                    }
                } else { Empty }
            }).Hash
        );
        my %pre = (%(), |$data.keys).reduce: -> %ag, $key {
            if $data{ $key }:exists && $data{ $key }.defined {
                my ($first, *@rest) := $key.split(".");
                %ag{ $first }{ @rest.join(".") } = $data{ $key } if @rest;
            }
            %ag
        }
        if %pre {
            for |$obj.^has-one-relationships -> $rel {
                with %pre{ $rel.rel-name } {
                    with $rel.relationship-model.^new-from-data: $_ {
                        .^saved-on-db;
                        $rel.set_value: $obj, $_
                    }
                }
            }
        }
        $obj
    }