Name

Class::Meta::Express - Concise, expressive creation of Class::Meta classes

Synopsis

package My::Contact;
use Class::Meta::Express;

class {
    meta contact => ( default_type => 'string' );
    has 'name';
    has contact => ( required => 1 );
}

Description

This module provides an interface to concisely yet expressively create classes with Class::Meta. It does so by temporarily exporting magical functions into a package that uses it, thereby providing a declarative alternative to Class::Meta's verbose object-oriented syntax.

Interface

Class::Meta::Express exports the following functions into any package that uses it. But beware: the functions are temporary! Once the class is declared, the functions are all removed from the calling package, thereby avoiding name space pollution and allowing you to create your own functions or methods with the same names, if you like, after declaring the class.

Functions

class

class {
    # Declare class.
}

Yes, the class keyword is secretly a function. It takes a single argument, a code reference, for which may omit the sub keyword. Cute, eh?. It simply executes the code reference passed as its sole argument, removes the class, meta, ctor, has, method, and build functions from the calling name space, and then calls build() on the Class::Meta object, thus building the class.

meta

meta 'thingy';

This function creates and returns the Class::Meta object that creates the class. Calling it is optional; if you don't use it to identify the basic meta data of your class, Class::Meta::Express will create the Class::Meta object for you, passing the last part of the class name -- with uppercase characters converted to lowercase and preceded by an underscore -- as the key parameter. So "My::FooXML" would get the key "foo_xml". Of course, if you have more two classes that would end up with that key name, you'll have to call meta for all but one in order to avoid conflicts.

If you do choose to use this function, there are a number of benefits, as you'll soon read.

The first argument must be the key to use for the class, which will be passed as the key parameter to Class::Meta->new. Otherwise, it takes the same parameters as Class::Meta:

name

A display name.

desc

A description of the class.

abstract

A boolean: Is the class an abstract class?

trust

An array reference of classes that this class trusts to call its trusted methods.

default_type

The default data type to use for attributes that specify no data type.

class_class

A Class::Meta::Class subclass.

constructor_class

A Class::Meta::Constructor subclass.

attribute_class

A Class::Meta::Attribute subclass.

method_class

A Class::Meta::Method subclass.

error_handler

A code reference to handle exceptions.

Consult the Class::Meta documentation for a detailed description of these parameters, in addition to which, meta adds support for the following parameters:

meta_class

If you've subclassed Class::Meta and want to use your subclass to define your classes instead of Class::Meta itself, specify the subclass with this parameter.

reexport

Installs an import() method into the calling name space that re-exports the express functions. The trick is that, if you've specified values for the meta_class or many of the parameters supported by Class::Meta, they will be used in the meta function exported by your class! For example:

package My::Base;
use Class::Meta::Express;
class {
    meta base => (
         meta_class   => 'My::Meta',
         default_type => 'string',
         trust        => 'My::Util',
         reexport     => 1,
    );
}

And now other classes can use My::Base instead of Class::Meta::Express and get the same defaults. Of course, this is only important if you're not inheriting from another Class::Meta class and not passing the meta_class parameter, because Class::Meta classes inherit the parameter values from their most immediate super class. But if you're not using inheritance and want to set up some universal settings to use throughout your project, this is a great way to do it.a

For example, say that you want My::Contact to inherit from My::Base and use its defaults. Just do this:

package My::Contact;
use My::Base;        # Forces import() to be called.
use base 'My::Base';

class {
    has  'name'      # Will be a string.
}

Any parameters passed to meta and labeled as "inheritable" by Class::Meta will be duplicated, as will "meta_class".

If you need your own import() method to export stuff, just pass it to the reexport parameter:

meta base => (
     meta_class   => 'My::Meta',
     default_type => 'string',
     trust        => 'My::Util',
     reexport     => sub { ... },
);

Class::Meta::Express will do the right thing by shifting execution to your import method after it finishes its dirty work.

The parameters may be passed as either a list, as above, or as a hash reference:

meta base => {
     meta_class   => 'My::Meta',
     default_type => 'string',
     reexport     => 1,
};

ctor

ctor 'new';

Calls add_constructor() on the Class::Meta object created by meta, passing the first argument as the name parameter. All other arguments can be any of the parameters supported by add_constructor():

create

A boolean indicating whether or not Class::Meta should create the constructor method.

label

A display name for the constructor.

desc

A description.

code

A code reference implementing the constructor method.

view

Visibility of the constructor: PUBLIC, PRIVATE, TRUSTED, or PROTECTED.

caller

A code reference to call the constructor.

Here's a simple example that adds a label to the constructor:

ctor new => ( label => 'Foo' );

The second argument can optionally be a code reference that will be passed as the code parameter to add_constructor():

ctor new => sub { bless {} => shift };

If you want to specify other parameters and the code parameter, do so explicitly:

ctor new => (
    label => 'Foo',
    code  => sub { bless {} => shift },
    view  => 'PRIVATE',
);

The parameters may be passed as either a list, as above, or as a hash reference:

ctor new => {
    label => 'Foo',
    code  => sub { bless {} => shift },
    view  => 'PRIVATE',
};

has

has name => ( is => 'string' );

Calls add_attribute() on the Class::Meta object created by meta, passing the first argument as the name parameter. All other arguments can be any of the parameters supported by add_attribute():

type
is

The attribute data type.

required

Boolean indicating whether or not the attribute is required to have a value.

once

Boolean indicating whether or not the attribute can be set only once.

label

A display name.

desc

A description.

view

Visibility of the attribute: PUBLIC, PRIVATE, TRUSTED, or PROTECTED.

authz

Authorization of the attribute: READ, WRITE, RDWR, or NONE.

create

Specifies how the accessor should be created: GET, SET, GETSET, or NONE.

context

The attribute context, either CLASS or OBJECT.

default

Default value for the attribute, or else a code reference that, when executed, returns a default value.

override

Boolean indicating whether or not the attribute can override an attribute with the same name in a parent class.

If the default_type parameter was specified in the call to meta, then the type (or is if you have Class::Meta 0.53 or later and prefer it) can be omitted unless you need a different type:

meta thingy => ( default_type => 'string' );
has 'name'; # Will be a string.
has id => ( is => 'integer' );
# ...

The parameters may be passed as either a list, as above, or as a hash reference:

has id => { is => 'integer' };

method

method 'say';

Calls add_method() on the Class::Meta object created by meta, passing the first argument as the name parameter. An optional second argument can be used to define the method itself (if you have Class::Meta 0.51 or later):

method say => sub { shift; print @_, $/; }

Otherwise, you'll have to define the method in the class itself (as was required in Class::Meta 0.50 and earlier). If you want to specify other parameters to add_method(), just pass them after the method name and explicitly mix in the code parameter if you need it:

method say => (
    view => 'PROTECTED',
    code => sub { shift; print @_, $/; },
);

All other arguments can be any of the parameters supported by add_method():

label

A display name.

desc

A description.

view

Visibility of the method: PUBLIC, PRIVATE, TRUSTED, or PROTECTED.

code

A code reference implementing the method.

context

The method context, either CLASS or OBJECT.

caller

A code reference to call the constructor.

args

A description of the supported arguments.

returns

A description of the return value.

The parameters may be passed as either a list, as above, or as a hash reference:

method say => {
    view => 'PROTECTED',
    code => sub { shift; print @_, $/; },
};

build

build;

This function is a deprecated holdover from before version 0.05. It used to be that there was no class keyword and you had to just call the rest of the above functions and then call build when you're done. But who liked that? It was actually a bitter pill among all this sweet, sweet sugar. But no more; build will likely be removed in a future version.

Overriding Functions

It is possible to override the functions exported by this module by subclassing it (after a fashion). Say that you wanted to change the meta() function so that it forces all attributes to default to a the type "string". Just override the function like so:

package My::Express;
use base 'Class::Meta::Express';

sub meta {
    splice @_, 1, 0, default_type => 'string';
    goto &Class::Meta::Express::meta;
}

The trick here is to set @_ and then goto &Class::Meta::Express::meta. This is so that the package that calls this function will be seen as the caller and therefore the Class::Meta object will be properly created for that package.

Why would you want to do all this? Well, perhaps you're building a lot of classes and don't want to have to repeat yourself so much. So now all you have to do is use your My::Express module instead of Class::Meta::Express:

package My::Person;
use My::Express;
class {
    meta person => ();
    has  name   => ();
}

And now you've created a new class with the string type attribute "name".

Justification

Although I am of course fond of Class::Meta, I've never been overly thrilled with its interface for creating classes:

package My::Thingy;
use Class::Meta;

 BEGIN {
     # Create a Class::Meta object for this class.
     my $cm = Class::Meta->new( key => 'thingy' );

     # Add a constructor.
     $cm->add_constructor( name   => 'new' );

     # Add a couple of attributes with generated accessors.
     $cm->add_attribute(
         name     => 'id',
         is       => 'integer',
         required => 1,
     );

     $cm->add_attribute(
         name     => 'name',
         is       => 'string',
         required => 1,
     );

     $cm->add_attribute(
         name    => 'age',
         is      => 'integer',
     );

    # Add a custom method.
     $cm->add_method(
         name => 'chk_pass',
         code => sub { return 'code' },
     );
     $cm->build;
 }

This example is relatively simple; it can get a lot more verbose. But even still, all of the method calls were annoying. I mean, whoever thought of using an object oriented interface for declaring a class? (Oh yeah: I did.) I wasn't alone in wanting a more declarative interface; Curtis Poe, with my blessing, created Class::Meta::Declare, which would use this syntax to create the same class:

package My::Thingy;
use Class::Meta::Declare ':all';

Class::Meta::Declare->new(
    # Create a Class::Meta object for this class.
    meta       => [
        key       => 'thingy',
    ],
    # Add a constructor.
    constructors => [
        new => { }
    ],
    # Add a couple of attributes with generated accessors.
    attributes => [
        id => {
            type    => $TYPE_INTEGER,
            required => 1,
        },
        name => {
            required => 1,
            type     => $TYPE_STRING,
        },
        age => { type => $TYPE_INTEGER, },
    ],
    # Add a custom method.
    methods => [
        chk_pass => {
            code => sub { return 'code' },
        }
    ]
);

This approach has the advantage of being a bit more concise, and it is declarative, but I find all of the indentation levels annoying; it's hard for me to figure out where I am, especially if I have to define a lot of attributes. And finally, everything is a string with this syntax, except for those ugly read-only scalars such as $TYPE_INTEGER. So I can't easily tell where one attribute ends and the next one starts. Bleh.

What I wanted was an interface with the visual distinctiveness of the original Class::Meta syntax but with the declarative approach and intelligent defaults of Class::Meta::Declare, while adding expressiveness to the mix. The solution I've come up with is the use of temporary functions imported into a class only until the end of the class declaration:

package My::Thingy;
use Class::Meta::Express;

class {
    # Create a Class::Meta object for this class.
    meta 'thingy';

    # Add a constructor.
    ctor new => ( );

    # Add a couple of attributes with generated accessors.
    has id   => ( is => 'integer', required => 1 );
    has name => ( is => 'string',  required => 1 );
    has age  => ( is => 'integer' );

   # Add a custom method.
    method chk_pass => sub { return 'code' };
}

That's much better, isn't it? In fact, we can simplify it even more by setting a default data type and eliminating the empty lists:

package My::Thingy;
use Class::Meta::Express;

class {
    # Create a Class::Meta object for this class.
    meta thingy => ( default_type => 'integer' );

    # Add a constructor.
    ctor 'new';

    # Add a couple of attributes with generated accessors.
    has id   => ( required => 1 );
    has name => ( is => 'string', required => 1 );
    has 'age';

    # Add a custom method.
    method chk_pass => sub { return 'code' };
}

Not bad, eh? I have to be honest: I borrowed the syntax from Moose. Thanks for the idea, Stevan!

See Also

Class::Meta

This is the module that's actually doing all the work. Class::Meta::Express just offers a sweeter interface for creating new classes with Class::Meta. You'll still want to know all about Class::Meta's introspection capabilities, type constraints, and more. Check it out!

Class::Meta::Declare

Curtis Poe's declarative interface to Class::Meta. Deprecated in favor of this module.

To Do

  • Make it so that the reexport parameter can work with an import method that's already installed in a module.

Support

This module is stored in an open GitHub repository. Feel free to fork and contribute!

Please file bug reports via GitHub Issues.

Author

David E. Wheeler <david@justatheory.com>

Copyright and License

Copyright (c) 2006-2023 David E. Wheeler Some Rights Reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.