The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Role::Subsystem - a parameterized role for object subsystems, helpers, and delegates

VERSION

version 0.101342

DESCRIPTION

Role::Subsystem is a parameterized role. It's meant to simplify creating classes that encapsulate specific parts of the business logic related to parent classes. As in the synopsis below, it can be used to write "helpers." The subsystems it creates must have a reference to a parent object, which might be referenced by id or with an actual object reference. Role::Subsystem tries to guarantee that no matter which kind of reference you have, the other kind can be obtained and stored for use.

What??

Okay, imagine you have a big class called Account. An Account is the central point for a lot of behavior, and rather than dump all that logic in one place, you partition it into subsytems. Let's say we want to write a subsystem that handles all of an Account's Services. We might write this:

  package Account::ServiceManager;
  use Moose;
  use Account;

  with 'Role::Subsystem' => {
    ident  => 'acct-service-mgr',
    type   => 'Account',
    what   => 'account',
    getter => sub { Account->retrieve_by_id( $_[0] ) },
  };

  sub add_service {
    my ($self, @args) = @_;

    # ... do some preliminary business logic

    $self->account->insert_related_rows(...);

    # ... do some cleanup business logic
  }

Then you might add to Account.pm:

  package Account;
  sub service_mgr {
    my ($self) = @_;
    return Account::ServiceManager->for_account($self);
  }

Then, to add a service you can write:

  $account->service_mgr->add_service(...);

You could also just grab the service manager object and use it as a handle for performing operations.

If you don't have an Account object, just a reference to its id, you could get the service manager like this:

  my $service_mgr = Account::ServiceManager->for_account_id( $account_id );

Why?

Here's an overview of everything this role will do for you, in terms of the Account::ServiceManager example above.

It will create the for_account and for_account_id constructors on your subsystem. (The for_account_id constructor will only be created if a getter is supplied.)

It will defer retrieval of account objects if you construct with only a account_id, so that if you never need the full object, you never waste time getting it.

It will ensure that any account and account_id encountered match the type and id_type types, respectively. This will prevent a bogus identifier from being accepted, only to die later when it can't be used for lazy retrieval.

If you create a subsystem object by passing in the parent object (the account), it will take a weak reference to it to prevent cyclical references from interfering with garbage collection. If the reference goes away, or if you did not start with a reference, a strong reference will be constructed to allow the subsystem to function efficiently afterward. (This behavior can be disabled, if you never want to take a weak reference.)

Swappable Subsystem Implementations

You can also have multiple implementations of a single kind of subsystem. For example, you may eventually want to do something like this:

  package Account::ServiceManager;
  use Moose::Role;

  with 'Role::Subsystem' => { ... };

  requries 'add_service';
  requries 'remove_service';
  requries 'service_summary';

...and then...

  package Account::ServiceManager::Legacy;
  with 'Account::ServiceManager';

  sub add_service { ... };

...and...

  package Account::ServiceManager::Simple;
  with 'Account::ServiceManager';

  sub add_service { ... };

...and finally...

  package Account;

  sub settings_mgr {
    my ($self) = @_;

    my $mgr_class = $self->schema_version > 1
                  ? 'Account::ServiceManager::Simple'
                  : 'Account::ServiceManager::Legacy';

    return $mgr_class->for_account($self);
  }

This requires a bit more work, but lets you replace subsystem implementations as fairly isolated units.

PERL VERSION

This library should run on perls released even a long time ago. It should work on any version of perl released in the last five years.

Although it may work on older versions of perl, no guarantee is made that the minimum required version will not be increased. The version may be increased for any reason, and there is no promise that patches will be accepted to lower the minimum required perl.

PARAMETERS

These parameters can be given when including Role::Subsystem; these are in contrast to the attributes and methods below, which are added to the classe composing this role.

ident

This is a simple name for the role to use when describing itself in messages. It is required.

what

This is the name of the attribute that will hold the parent object, like the account in the synopsis above.

This attribute is required.

what_id

This is the name of the attribute that will hold the parent object's identifier, like the account_id in the synopsis above.

If not given, it will be the value of what with "_id" stuck on the end.

type

This is the type that the what must be. It may be a stringly Moose type or an MooseX::Types type. (Or anything else, right now, but anything else will probably cause runtime failures or worse.)

This attribute is required.

id_type

This parameter is like type, but is used to check the what's id, discussed more below. If not given, it defaults to Defined.

id_method

This is the name of a method to call on what to get its id. It defaults to id.

getter

This (optional) attribute supplied a callback that will produce the parent object from the what_id.

weak_ref

If true, when a subsytem object is created with a defined parent object (that is, a value for what), the reference to the object will be weakened. This allows the parent and the subsystem to store references to one another without creating a problematic circular reference.

If the parent object is subsequently garbage collected, a new value for what will be retreived and stored, and it will not be weakened. To allow this, setting weak_ref to true requires that getter be supplied.

weak_ref is true by default.

ATTRIBUTES

The following attributes are added classes composing Role::Subsystem.

$what

This will refer to the parent object of the subsystem. It will be a value of the type type defined when parameterizing Role::Subsystem. It may be lazily computed if it was not supplied during creation or if the initial value was weak and subsequently garbage collected.

If the value of what when parameterizing Role::Subsystem was account, that will be the name of this attribute, as well as the method used to read it.

$what_id

This method gets the id of the parent object. It will be a defined value of the id_type provided when parameterizing Role::Subsystem. It may be lazily computed by calling the id_method on what as needed.

METHODS

for_$what

  my $settings_mgr = Account::ServiceManager->for_account($account);

This is a convenience constructor, returning a subsystem object for the given what.

for_$what_id

  my $settings_mgr = Account::ServiceManager->for_account_id($account_id);

This is a convenience constructor, returning a subsystem object for the given what_id.

AUTHOR

Ricardo Signes <cpan@semiotic.systems>

CONTRIBUTORS

  • Matthew Horsfall <wolfsage@gmail.com>

  • Ricardo Signes <rjbs@semiotic.systems>

COPYRIGHT AND LICENSE

This software is copyright (c) 2010 by Ricardo Signes.

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