RaCoCo result: MetamodelX::Red::Id

# MetamodelX::Red::Id 82%

    use Red::Model;
    use Red::AST::Value;
    use Red::AST::Infixes;
    use Red::Attr::Column;

    =head2 MetamodelX::Red::Id

    unit role MetamodelX::Red::Id;

    has $!id-values-attr;

    method columns   { ... }
    #method set-attr  { ... }
    #method set-dirty { ... }

    method id-values-attr(|) {
        $!id-values-attr;
    }

    sub id-values-attr-build(\type, | --> Hash){
        {}
    }

    method set-helper-attrs(Mu \type) {
        my %attr = type.^attributes.map( -> $a { $a.name => $a }).Hash;
        if %attr<%!___ID_VALUES___> -> $a {
            $!id-values-attr //= $a;
        }
        else {
            $!id-values-attr = Attribute.new: :name<%!___ID_VALUES___>, :package(type), :type(Hash), :!has_accessor;
            $!id-values-attr.set_build: &id-values-attr-build;
            type.^add_attribute: $!id-values-attr;
        }
    }

    #| Checks if the given attribute is a primary key of the model.
    multi method is-id($, Red::Attr::Column $attr) {
        so $attr.column.id
    }

    multi method is-id($, $ --> False) {}

    #| Gets a list of ids
    method id(Mu \type) {
        self.columns(type).grep(*.column.id).list
    }

    #| Returns a list of attributes that are either primary keys or marked as unique.
    method general-ids(\model) {
        (|model.^id, |model.^unique-constraints)
    }

    #| Sets ids
    method populate-ids(Red::Model:D $model) {
        $model.^reset-id;
        $model.^set-id: $model.^id.map({ .name => .get_value: $model }).Hash;
    }

    #| resets id
    multi method reset-id(Red::Model:D $model) {
        $!id-values-attr.set_value: $model, {}
    }

    #| Sets ids
    multi method set-id(Red::Model:D $model, %ids --> Hash()) {
        my %attrs = |$model.^id.map({ .name => $_ });
        for %ids.kv -> Str $name, $val {
            $!id-values-attr.get_value($model).{ $name } = $model.^get-attr: $name without $!id-values-attr.get_value($model).{ $name };
            self.set-attr: $model, $name, $val;
            $model.^set-dirty: %attrs{$name};
        }
    }

    #| Sets id
    multi method set-id(Red::Model:D $model, $id where !.^isa: Associative --> Hash()) {
        my $col = $model.^id.head;
        $!id-values-attr.get_value($model).{ $col.name } = $model.^get-attr: $col without $!id-values-attr.get_value($model).{ $col.name };
        self.set-attr: $model, $col, $id;
        $model.^set-dirty: $col;
    }

    #| Returns a Hash with an id map
    multi method id-map(Red::Model $model, $id --> Hash()) {
        die "$model.^name() has no id" unless $model.^id;
        $model.^id.head.name.substr(2) => $id
    }

    multi method id-hash(Red::Model $model --> Hash()) {
        $model.^id.map({
            my $name = .name.substr(2);
            $name => $model."$name"()
        })
    }

    #| Returns a filter using the id
    multi method id-filter(Red::Model:D $model) {
        my @a = $model.^general-ids.flat.map({
            Red::AST::Eq.new:
    	    :bind-right,
                .column,
                ast-value
                    :type(.type),
                    $!id-values-attr.get_value($model).{ .name }
                        // self.get-old-attr($model, $_)
                        // self.get-attr: $model, $_
        });
        @a.elems >= 2
            ?? @a.reduce: { Red::AST::AND.new: $^a, $^b }
            !! @a[0]
    }

    #| Returns a filter using the id
    multi method id-filter(Red::Model:U $model, $id) {
        die "Model must have only 1 id to use id-filter this way" if $model.^id.elems != 1;
        self.id-filter: $model, |{$model.^id.head.column.attr-name => $id}
    }

    # TODO: fix, $model.^general-ids can return an list of lists (for counstraints of more that one column)
    #| Returns a filter using the id
    multi method id-filter(Red::Model:U $model, *%data) { # where { .keys.all ~~ $model.^general-ids.flat.map(*.column.attr-name).any }) {
        my %cols := set $model.^general-ids.flat.map(*.column.attr-name);
        my @not-ids = %data.keys.grep: { not %cols{ $_ } };
        die "one of the following keys aren't ids: { @not-ids.join: ", " }" if @not-ids;
        $model.^general-ids.flat
            .map({
                next without %data{.column.attr-name};
                Red::AST::Eq.new:
                    .column,
                    ast-value %data{.column.attr-name}
            })
            .reduce: {
                Red::AST::AND.new: $^a, $^b
            }
        ;
    }

    #multi method id-filter(Red::Model:U $model, *%data) {
    #    die "one of the following keys aren't ids: { %data.keys.join: ", " }"
    #}