NAME
Moose::Cookbook::Extending::Recipe1 - Moose extension overview
DESCRIPTION
Moose has quite a number of ways in which extensions can hook into Moose and change its behavior. Moose also has a lot of behavior that can be changed. This recipe will provide an overview of each extension method and give you some recommendations on what tools to use.
If you haven't yet read the recipes on metaclasses, go read those first. You can't really write Moose extensions without understanding the metaclasses, and those recipes also demonstrate some basic extensions mechanisms such as metaclass subclasses and traits.
Playing Nice With Others
One of the goals of this overview is to help you build extensions that cooperate well with other extensions. This is especially important if you plan to release your extension to CPAN.
Moose comes with several modules that exist to help your write cooperative extensions. These are Moose::Exporter and Moose::Util::MetaRole. By using these two modules to implement your extensions, you will ensure that your extension works with both the Moose core features and any other CPAN extension using those modules.
PARTS OF Moose YOU CAN EXTEND
The types of things you might want to do in Moose extensions broadly fall into a few categories.
Metaclass Extensions
One way of extending Moose is by extending one or more Moose metaclasses. For example, in Moose::Cookbook::Meta::Recipe4 we saw a metaclass subclass that added a table
attribute to the metaclass. If you were writing an ORM, this would be a logical extension.
Many of the Moose extensions on CPAN work by providing an attribute metaclass extension. For example, the MooseX::AttributeHelpers
distro provides a new attribute metaclass that lets you delegate behavior to a non-object attribute (a hashref or simple number).
A metaclass extension can be packaged as a subclass or a role/trait. If you can, we recommend using traits instead of subclasses, since it's generally much easier to combine disparate traits then it is to combine a bunch of subclasses.
When your extensions are implemented as roles, you can apply them with the Moose::Util::MetaRole module.
Providing Sugar Subs
As part of a metaclass extension, you may also want to provide some sugar subroutines, much like Moose.pm
does. Moose provides a helper module called Moose::Exporter that makes this much simpler. This will be used in several of the extension recipes.
Object Class Extensions
Another common Moose extension is to change the default object class behavior. For example, the MooseX::Singleton
extension changes the behavior of your objects so that they are singletons. The MooseX::StrictConstructor
extension makes the constructor reject arguments which don't match its attributes.
Object class extensions often also include metaclass extensions. In particular, if you want your object extension to work when a class is made immutable, you may need to extend some or all of the Moose::Meta::Instance
, Moose::Meta::Method::Constructor
, and Moose::Meta::Method::Destructor
objects.
The Moose::Util::MetaRole module lets you apply roles to the base object class, as well as the meta classes just mentioned.
Providing a Role
Some extensions come in the form of a role for you to consume. The MooseX::Object::Pluggable
extension is a great example of this. In fact, despite the MooseX
name, it does not actually change anything about Moose's behavior. Instead, it is just a role that an object which wants to be pluggable can consume.
If you are implementing this sort of extension, you don't need to do anything special. You simply create a role and document that it should be used via the normal with
sugar:
package RoleConsumer;
use Moose;
with 'MooseX::My::Role';
New Types
Another common Moose extension is a new type for the Moose type system. In this case, you simply create a type in your module. When people load your module, the type is created, and they can refer to it by name after that. The MooseX::Types::URI
and MooseX::Types::DateTime
distros are two good examples of how this works.
ROLES VS TRAITS VS SUBCLASSES
It is important to understand that roles and traits are the same thing. A role can be used as a trait, and a trait is a role. The only thing that distinguishes the two is that a trait is packaged in a way that lets Moose resolve a short name to a class name. In other words, with a trait, the caller can specify it by a short name like "Big", and Moose will resolve it to a class like MooseX::Embiggen::Meta::Attribute::Role::Big
.
See Moose::Cookbook::Meta::Recipe3 and Moose::Cookbook::Meta::Recipe5 for examples of traits in action. In particular, both of these recipes demonstrate the trait resolution mechanism.
Implementing an extension as a (set of) metaclass or base object role(s) will make your extension more cooperative. It is hard for an end-user to effectively combine together multiple metaclass subclasses, but it can be very easy to combine roles.
USING YOUR EXTENSION
There are a number of ways in which an extension can be applied. In some cases you can provide multiple ways of consuming your extension.
Extensions as Metaclass Traits
If your extension is available as a trait, you can ask end users to simply specify it in a list of traits. Currently, this only works for metaclass and attribute metaclass traits:
use Moose -traits => [ 'Big', 'Blue' ];
has 'animal' =>
( traits => [ 'Big', 'Blue' ],
...
);
If your extension applies to any other metaclass, or the object base class, you cannot use the trait mechanism.
The benefit of the trait mechanism is that is very easy to see where a trait is applied in the code, and consumers have fine-grained control over what the trait applies to. This is especially true for attribute traits, where you can apply the trait to just one attribute in a class.
Extensions as Metaclass (and Base Object) Subclasses
Moose does not provide any simple APIs for consumers to use a subclass extension, except for attribute metaclasses. The attribute declaration parameters include a metaclass
parameter a consumer of your extension can use to specify your subclass.
This is one reason why implementing an extension as a subclass can be a poor choice. However, you can force the use of certain subclasses at import time by calling Moose->init_meta
for the caller, and providing an alternate metaclass or base object class.
If you do want to do this, you should look at using Moose::Exporter
to re-export the Moose.pm
sugar subroutines. When you use Moose::Exporter and your exporting class has an init_meta
method, Moose::Exporter makes sure that this init_meta
method gets called when your class is imported.
Then in your init_meta
you can arrange for the caller to use your subclasses:
package MooseX::Embiggen;
use Moose ();
use Moose::Exporter;
use MooseX::Embiggen::Meta::Class;
use MooseX::Embiggen::Object;
Moose::Exporter->setup_import_methods( also => 'Moose' );
sub init_meta {
shift; # just your package name
my %options = @_;
return Moose->init_meta(
for_class => $options{for_class},
metaclass => 'MooseX::Embiggen::Meta::Class',
base_class => 'MooseX::Embiggen::Object',
);
}
Extensions as Metaclass (and Base Object) Roles
Implementing your extensions as metaclass roles makes your extensions easy to apply, and cooperative with other metaclass role-based extensions.
Just as with a subclass, you will probably want to package your extensions for consumption with a single module that uses Moose::Exporter. However, in this case, you will use Moose::Util::MetaRole to apply all of your roles. The advantage of using this module is that it preserves any subclassing or roles already applied to the users metaclasses. This means that your extension is cooperative by default, and consumers of your extension can easily use it with other role-based extensions.
package MooseX::Embiggen;
use Moose ();
use Moose::Exporter;
use Moose::Util::MetaRole;
use MooseX::Embiggen::Role::Meta::Class;
use MooseX::Embiggen::Role::Meta::Attribute;
use MooseX::Embiggen::Role::Meta::Method::Constructor
use MooseX::Embiggen::Role::Object;
Moose::Exporter->setup_import_methods( also => 'Moose' );
sub init_meta {
shift; # just your package name
my %options = @_;
Moose->init_meta(%options);
my $meta = Moose::Util::MetaRole::apply_metaclass_roles(
for_class => $options{for_class},
metaclass_roles => ['MooseX::Embiggen::Role::Meta::Class'],
attribute_metaclass_roles =>
['MooseX::Embiggen::Role::Meta::Attribute'],
constructor_class_roles =>
['MooseX::Embiggen::Role::Meta::Method::Constructor'],
);
Moose::Util::MetaRole::apply_base_class_roles(
for_class => $options{for_class},
roles => ['MooseX::Embiggen::Role::Object'],
);
return $meta;
}
As you can see from this example, you can use Moose::Util::MetaRole
to apply roles to any metaclass, as well as the base object class. If some other extension has already applied its own roles, they will be preserved when your extension applies its roles, and vice versa.
Providing Sugar
With Moose::Exporter, you can also export your own sugar subs, as well as those from other sugar modules:
package MooseX::Embiggen;
use Moose ();
use Moose::Exporter;
Moose::Exporter->setup_import_methods(
with_caller => ['embiggen'],
also => 'Moose',
);
sub init_meta { ... }
sub embiggen {
my $caller = shift;
$caller->meta()->embiggen(@_);
}
And then the consumer of your extension can use your embiggen
sub:
package Consumer;
use MooseX::Embiggen;
extends 'Thing';
embiggen ...;
This can be combined with metaclass and base class roles quite easily.
LEGACY EXTENSION METHODOLOGIES
Before the existence of Moose::Exporter and Moose::Util::MetaRole, there were a number of other ways to extend Moose. In general, these methods were less cooperative, and only worked well with a single extension.
These methods include metaclass.pm
, Moose::Policy
(which uses metaclass.pm
under the hood), and various hacks to do what Moose::Exporter does. Please do not use these for your own extensions.
Note that if you write a cooperative extension, it should cooperate with older extensions, though older extensions generally do not cooperate with each other.
CONCLUSION
If you can write your extension as one or more metaclass and base object roles, please consider doing so. Make sure to read the docs for Moose::Exporter and Moose::Util::MetaRole as well.
Caveat
The Moose::Util::MetaRole API is still considered an experiment, and could go away or change in the future.
AUTHOR
Dave Rolsky <autarch@urth.org>
COPYRIGHT AND LICENSE
Copyright 2008 by Infinity Interactive, Inc.
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.