Instantiate a Raku class, and update an instance variable in the constructor

Viewed 271

I seem to be having difficulty understanding how to correctly work with classes in Raku.

I am trying to create a 'Database' class that I will use throughout my cro application. However I don't seem to understand how to deal with setting instance variables at object construction (BUILD?) time.

The class below shows an example of the problem I am having when calling it with:

Calling:

my $db = Database.new(dsn => 'update me!');
say "what value is foo: " ~ $db.what-is-foo(); # prints "what value is foo: 0" but expecting 123

My Class:

use DB::Pg;

class Database {
    has Str    $.dsn is required;
    has DB::Pg $!dbh = Nil;
    has Int    $!foo = 0;

    method new (:$dsn) {
        my $dbh = DB::Pg.new(conninfo => :$dsn);
        my $foo = 123;
        return self.bless(:$dsn, dbh => $dbh, foo => $foo);
    }

    method what-is-foo() {
        return $!foo;
    }
}

So in my class constructor I would like to pass in a named arg for the database dsn. In the new method I plan to connect, and set an instance variable to the connection handle.

In this example I am using a simple integer (foo) as a test.

So far I have found the docs here to not include an example of this type of pattern, except pehaps:

use DB::Pg;

class Database {
    has Str    $.dsn is required;
    has DB::Pg $!dbh = Nil;
    has Int    $!foo = 0;

    submethod BUILD(:$dsn) {
        $!dbh = DB::Pg.new(conninfo => :$dsn);
        $!foo = 123;
    }

    method what-is-foo() {
        return $!foo;
    }
}

But that gives me:

The attribute '$!dsn' is required, but you did not provide a value for it.

Any help would be greatly appreciated!

1 Answers

Only attributes with public accessors (that is, the . twigil) are set automatically with bless.

You have two ways to handle this. You can set them at TWEAK or BUILD, or you can add the attribute is built to the attribute to have this done automatically for you. The attribute is built is probably the easiest in this case, just say has Int $!foo is built = 0. But it's worth expanding on the other options:

If you include a BUILD method, you are responsible for all of the set up on your own. This means both public and private attributes. But you can make your life easier by smartly naming the parameters:

method BUILD (:$!dsn, :$!dbh, :$!foo) { }

That's actually it. The signature binds the incoming values to $!dsn, etc., which of course sets them for the whole object instance. Of course, you can do fancier things here too. In any case, after BUILD, there are some additional checks done. (1) if you don't set $!dsn, because you have is required, there will be an error. (2) if you don't ultimately set $!foo or $!dbh, they will receive their default value.

With TWEAK, you get passed the same arguments as you would with BUILD, but all initial set up is done (BUILD or the auto-binding to public attributes, and all default values, plus guarantee that required values are there). You just get a chance to do some last second adjustments.

Related