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

NAME

SPOPS::Manual::CodeGeneration - How SPOPS builds classes

SYNOPSIS

This part of the SPOPS manual describes how SPOPS generates the class code and how you can customize the process.

DESCRIPTION

So with configuration, we would create a number of slots into which classes could install behaviors. The slots are:

manipulate_configuration
id_method
read_code
fetch_by
has_a (relationship)
add_rule

They're described in more detail below.

A class in the hierarchy for an object (or in the 'rules_from' list) could install a behavior in none or all of the slots. So for instance, SPOPS::Configure::DBI has been replaced by SPOPS::ClassFactory::DBI, which reads the configuration for SPOPS::DBI-derived objects and installs DBI-specific links_to behaviors.

Multiple behaviors can be installed in each slot, with the idea that order shouldn't matter. Since we do a depth-first inheritance walk we should be ok -- more specific classes will execute their behaviors before the more general ones.

The processing of each slot uses a form of the 'Chain of Responsibility' pattern -- a behavior can decide to perform or not perform any action and continue (OK), to perform an action, to declare the slot finished (DONE), to stop the process entirely (ERROR) or that the behavior has made changes which necessitates refreshing the behavior listing (RESTART).

As a simple example of a behavior, say we wanted to ensure that all of our objects are using a particular SPOPS::DBI subclass:

  1: package My::UseMyDBIClass; # -*-perl-*-
  2: 
  3: use strict;
  4: 
  5: my $USE_CLASS = 'SPOPS::DBI::Pg';
  6: 
  7: sub behavior_factory {
  8:   my ( $class ) = @_;
  9:   return { manipulate_configuration => \&check_spops_subclass };
 10: }
 11: 
 12: sub check_spops_subclass {
 13:     my ( $config ) = @_;
 14:     foreach ( @{ $config->{isa} } ) {
 15:         s/^SPOPS::DBI::.*$/$USE_CLASS/;
 16:     }
 17:     return ( SPOPS::ClassFactory::RESTART, undef );
 18: }

We would just put this method in a common parent to all our objects and install the behavior in the 'manipulate_configuration' slot. When the class is configured the rule would be executed and we would never have to worry about our objects using the wrong DBI class again. (This is common in OpenInteract when you install new packages and forget to run 'oi_manage change_spops_driver'.)

And that's it! The system enables very focused and flexible behaviors. For instance, we could create one links_to behavior for DBI to handle the current configuration style and another to handle the proposed (and more robust) SPOPS::Inheritable configuration style. The first could step through the 'links_to' configuration items and process only those it can, while the second could do the same. And neither has to know about the other.

We could also do wacky stuff, like install a 'read_code' behavior to use LWP to grab a module and checksums off a code repository somewhere. If the checksum and code match up, we can bring the code into the SPOPS class.

SLOTS

We use the term 'slots' to refer to the different steps we walk through to create, configure and auto-generate methods for an SPOPS class. Each 'slot' can have multiple behaviors attached to it.

Finding Slot Behaviors

Slot behaviors can come from any of the classes in the @ISA for the generated class, or from any of the classes listed in the 'rules_from' configuration key.

The differences between the 'isa' and the 'rules_from' class lists are:

  • The classes listed in 'rules_from' are used by themselves. The 'isa' classes we use Class::ISA to find the inheritance tree of our generated SPOPS class so we can look into each of them for relevant behaviors.

  • The 'rules_from' classes are not used by anything once the class has been generated, while 'isa' classes are used in the normal Perl manner for inheritance.

Slot Listing

Here are the current slots and a description of each. Note that they might change -- in particular, the 'links_to' and 'has_a' slots might be merged into a single 'relationship' slot.

  • manipulate_configuration: Modify the configuration as necessary. SPOPS comes with one method to transform arrayrefs (for easy typing) into hashref (for easy lookup). Other options might be to set application-specific information accessible from all your objects, futz around with the @ISA, etc.

  • id_method: Very focused: generate an id( [ $new_id ] ) method. SPOPS uses these to ensure it can get the crucial information from every object -- class and ID -- without having to know what the ID field is.

    SPOPS comes with a default method for this that will probably work fine for you -- see SPOPS::ClassFactory::DefaultBehavior.

  • read_code: Reads in code from another class to the class being created/configured. SPOPS comes with a method to read the value(s) from the configuration key 'code_class', find them from @INC and read them in.

    But you can perform any action you need here -- you could even issue a SOAP request to read Perl code (along with checksums) off the net, check the code then read it in.

  • fetch_by: Process the 'fetch_by' configuration key. SPOPS comes with autogenerated methods to do this, but you can modify it and implement your own.

  • has_a: Process the 'has_a' configuration key. Usually this is implementation-specific and involves auto-generating methods. SPOPS comes with a default for this, but an implementation class can elect to not use it by returning the 'DONE' constant.

  • links_to: Process the 'links_to' configuration key. Usually this is implementation-specific and involves auto-generating methods.

  • add_rule: You will probably never need to create a behavior here: SPOPS has one that performs the same duties as SPOPS::Configure::Ruleset used to -- it scans the @ISA of a class, finds the ruleset generation methods from all the parents and installs these coderefs to the class.

BEHAVIOR GENERATOR

The behavior generator is called 'behavior_factory' (the name can be imported in the constant 'FACTORY_METHOD') and it takes a single argument, the name of the class for which the behaviors are being generated. It should return a hashref with the slot names as keys. A value should either be a coderef (for a single behavior) or an arrayref of coderefs (for multiple behaviors).

Here is an example, directly from from SPOPS:

 sub behavior_factory {
     my ( $class ) = @_;
     $log->is_info &&
         $log->info( "Installing SPOPS default behaviors for ($class)" );
     return { manipulate_configuration =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_modify_config,
             read_code                =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_read_code,
             id_method                =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_id_method,
             has_a                    =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_relate_hasa,
             fetch_by                 =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_relate_fetchby,
             add_rule                 =>
                    \&SPOPS::ClassFactory::DefaultBehavior::conf_add_rules, };
 }

So with this we're installing one behavior each into the slots 'manipulate_configuration', 'read_code', 'id_method', 'has_a', 'fetch_by' and 'add_rule'. Here's an example that installs multiple behaviors in a single slot:

 sub behavior_factory {
     my ( $class ) = @_;
     return { links_to => [ \&simple_linking, \&complex_linking ] };
 }

 sub simple_linking { ... }
 sub complex_linking { ... }

BEHAVIOR DESCRIPTION

Behaviors can be simple or complicated, depending on what you need them to do. The simple behavior we showed above does a single, simple task and then exits. This is probably the best strategy for most behavior uses -- focus each one one a single task so you it's easy to follow and debug. The fact that we run the behaviors only once, when the class is being generated, means that you don't have to worry so much about efficiency.

Every behavior returns a two-item list. The first is the status of the behavior, the second is an optional message.

The potential status return values are all constants that can be imported from SPOPS::ClassFactory:

  • OK: The behavior executed without errors and that additional behaviors can execute.

  • DONE: The behavior executed without errors and that additional behaviors in this slot should not execute.

  • RESTART: The behavior executed without errors but modified some aspect of the class or its configuration such that it is necessary to revisit the behavior map and see if there are any additional ones. The most common cause of this is adding one or more classes to 'isa' or 'rules_from'.

  • NOTIFY: The behavior executed with errors, but the errors will be written (via warn) to STDERR rather than halting the process.

  • ERROR: The behavior executed with errors, and that the entire process should immediately stop.

COPYRIGHT

Copyright (c) 2001-2004 Chris Winters. All rights reserved.

See SPOPS::Manual for license.

AUTHORS

Chris Winters <chris@cwinters.com>