In my previous Advent article, I created higher-order promises and showed you how to use them. I didn't show you the magic of how they work. Now I'll develop another example but from the other direction.

There are times that I want Mojo::File to act a bit differently than it does. Often I have a path where I want to combine only the basename with a different directory. I end up making Mojo::File objects for both and then working with the directory object to get what I want:

use Mojo::File qw(path); my $path = Mojo::File->new( '/Users/brian/bin/interesting.txt' ); my $dir = Mojo::File->new( '/usr/local/bin' ); my $new_path = $dir->child( $path->basename ); say $new_path; # /usr/local/bin/interesting.txt

That's annoying. I don't like that it takes so many steps. There are a few methods that I'd like instead. I'd rather be able to write it like this, where I start with the interesting file and keep working on it instead of switching to some other object:

use Mojo::File qw(path); my $new_path = Mojo::File ->new( '/Users/brian/bin/interesting.txt' ) ->rebase( '/usr/local/bin' ); # this isn't a method say $new_path; # /usr/local/bin/interesting.txt

I could go through various Perl tricks to add this method to Mojo::File through monkey patching or subclassing. But, as usual, Mojolicious anticipates my desire and provides a way to do this. I can add a role,

You can read about roles on your own while I jump into it. First, I create a class to represent my role. I define the method(s) I want. I use the name of the package I want to affect, add ::Role:: , then the name I'd like to use; it's not important that its lowercase. Mojo::Base sets up everything I need when I import -role :

package Mojo::File::Role::rebase { use Mojo::Base qw(-role -signatures); sub rebase ($file, $dir) { $file->new( $dir, $file->basename ) } }

I apply my new functionality by using with_roles on the class I want to affect. Since I used the naming convention by prefixing it with the target class ( Mojo::File ), then ::Role:: , then the short name I want. When I apply this, I can leave off most of the package name and use the short name preceded by a plus sign:

my $file_class = Mojo::File->with_roles( '+rebase' );

Alternately I could have typed out the full package name:

my $file_class = Mojo::File->with_roles( 'Mojo::File::Role::rebase' );

I'd need to use this if I didn't follow the naming convention:

my $file_class = Mojo::File->with_roles( 'I::Totally::Rejected::The::Convention::rebase' );

The $file_class is a string with the new class name. Behind that class there is some multiple inheritance magic that you'll be much happier ignoring. I don't need to use a bareword class name to call class methods; a string in a scalar variable works just as well. Now I can use my rebase :

say $file_class ->new( '/Users/brian/bin/interesting.txt' ) ->rebase( '/usr/local/bin/' );

That's much cleaner than what I was doing before and I like how this flows. But what if I get an already-created Mojo::File object from something else? I can apply the role ad hoc too:

my $file = path( '/Users/brian/bin/interesting.txt' ); say $file ->with_roles( '+rebase' ) ->rebase( '/usr/local/bin/' );

I can go further. Any methods I add to my role become part of the class. I often want to get the digests of files and although Mojo::Util makes that easier with some convenience functions, I want even more convenience. I add a couple of methods to my role to do the slurping for me:

use Mojo::File; package Mojo::File::Role::MyUtils { use Mojo::Base qw(-role -signatures); use Mojo::Util qw(md5_sum sha1_sum); sub rebase ($file, $dir) { $file->new( $dir, $file->basename ) } sub md5 ($file) { md5_sum( $file->slurp ) } sub sha1 ($file) { sha1_sum( $file->slurp ) } } my $file = Mojo::File ->with_roles( '+MyUtils' ) ->new(shift); say $file->sha1; say $file->md5;

You can read more about roles in Joel Berger's 2017 Mojolicious Advent Calendar entry Day 13: More About Roles. Curiously that was on Day 13 too, although I don't think Joel or I were clever enough to plan that.