Class::Meta::Express(3p)

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 "use"s 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 <https://github.com/theory/class-meta-express/>. Feel free to fork and contribute!

Please file bug reports via GitHub Issues <https://github.com/theory/class-meta-express/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.