Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFE: Document using Package::Variant as an alternative to roles #17

Open
djerius opened this issue Jan 19, 2025 · 0 comments
Open

RFE: Document using Package::Variant as an alternative to roles #17

djerius opened this issue Jan 19, 2025 · 0 comments

Comments

@djerius
Copy link
Contributor

djerius commented Jan 19, 2025

I'm a big fan of using roles, but at present (I believe) that Form::Tiny only supports extension via inheritance.

I've emulated roles via Package::Variant. The basic idea is to have a set of pre-packaged form fields (in the following code, I call them form fragments) and use Package::Variant to assemble a Form::Tiny class.

The Package::Variant class looks like this:

package FormGenerate;

use Exporter::Tiny 'mkopt';

use Package::Variant
  importing => [ 'Form::Tiny' => [ plugins => ( ... ) ] ], # optional list of plugins
  subs      => [
    'form_field', 'form_hook', 'extends', 'form_filter', 'form_validator', 'option', 'optargs_opts',
  ];

sub make_variant ( $class, $target_package, $package, @args ) {

    for my ( $entry ) ( mkopt( \@args )->@* ) {
        my ( $cmd, $args ) = $entry->@*;

        no strict 'refs';
        $cmd->( ( $args // [] )->@* );
    }
}

sub make_variant_package_name ( $class, $package, @args ) {
    return $package;
}

1;

The fragments are stored in a separate utility module:

package FormFragments;

use Types::TypeTiny 'BoolLike';
use Types::Common::String 'NonEmptyStr';
use FormGenerate;

use Exporter::Shiny 'FormFactory';

my %fragments = (

    Output => [
        form_field => [
            output => (
                type     => NonEmptyStr,
                required => 1,
            ),
        ],
    ],

    PlotDirs => [
        form_field => [ dir     => ( type => NonEmptyStr ) ],
        form_field => [ subdir  => ( type => NonEmptyStr ) ],
    ],

    TmpClean => [
        form_field => [ 'tmp_clean' => ( type => BoolLike, default => sub { true } ) ],
    ],

    PlotIt => [
        form_field => [ 'it'    => ( type => BoolLike, default => sub { false } ) ],
    ],

);

my $FormName = 'GeneratedForm0000';

sub FormFactory ( $base, @fragments ) {

    return FormGenerate(
        ++$FormName,
        extends      => [ "Form::$base" ],
        optargs_opts => [ inherit_optargs => true ],
        map { ( $fragments{$_} // croak( "unknown fragment: $_" ) )->@* } @fragments,
    )->new;
}

1;

And the composition looks like this:

package Form::Foo;

form_field plot => (
    type    => FormFactory( Plot => qw( Output TmpClean PlotDirs ) ),
    default => sub { {} },
);
1;

To make things easier, my implementation requires a preexisting Form::Tiny class which is sub-classed.

[The code was extracted and simplified from working code, so might have some syntax issues]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant