OpenInteract::CommonHandler - Base class that with a few configuration items takes care of many common operations


 package MySite::Handler::MyTask;

 use strict;
 use OpenInteract::CommonHandler;

 @MySite::Handler::MyTask::ISA = qw( OpenInteract::CommonHandler );

 sub MY_PACKAGE                 { return 'mytask' }
 sub MY_HANDLER_PATH            { return '/MyTask' }
 sub MY_OBJECT_TYPE             { return 'myobject' }
 sub MY_OBJECT_CLASS            {
     return OpenInteract::Request->instance->myobject
     return qw( name type quantity purpose_in_life that_other.object_name )
 sub MY_SEARCH_TABLE_LINKS      { return ( that_other => 'myobject_id' ) }
 sub MY_SEARCH_FORM_TITLE       { return 'Search for Thingies' }
 sub MY_SEARCH_FORM_TEMPLATE    { return 'search_form' }
 sub MY_SEARCH_RESULTS_TITLE    { return 'Thingy Search Results' }
 sub MY_SEARCH_RESULTS_TEMPLATE { return 'search_results' }
 sub MY_OBJECT_FORM_TITLE       { return 'Thingy Detail' }
 sub MY_OBJECT_FORM_TEMPLATE    { return 'form' }
 sub MY_EDIT_RETURN_URL         { return '/Thingy/search_form/' }
 sub MY_EDIT_FIELDS             {
     return qw( myobject_id name type quantity purpose_in_life )
 sub MY_EDIT_FIELDS_TOGGLED     { return qw( is_indoctrinated ) }
 sub MY_EDIT_FIELDS_DATE        { return qw( birth_date ) }
 sub MY_ALLOW_SEARCH_FORM       { return 1 }
 sub MY_ALLOW_SEARCH            { return 1 }
 sub MY_ALLOW_SHOW              { return 1 }
 sub MY_ALLOW_CREATE            { return 1 }
 sub MY_ALLOW_EDIT              { return 1 }
 sub MY_ALLOW_REMOVE            { return undef }
 sub MY_ALLOW_WIZARD            { return undef }
 sub MY_ALLOW_NOTIFY            { return 1 }

 # My date format is for users to type in 'yyyymmdd'

 sub _read_field_date {
    my ( $class, $apr, $field ) = @_;
    my $date_value = $apr->param( $field );
    $date_value =~ s/\D//g;
    my ( $y, $m, $d ) = $date_value =~ /^(\d\d\d\d)(\d\d)(\d\d)$/;
    return undef unless ( $y and $m and $d );
    return join( '-', $y, $m, $d );



This class implements most of the common functionality required for finding and displaying multiple objects, viewing a particular object, making changes to it and removing it. And you just need to modify a few configuration methods so that it knows what to save, where to save it and what type of things you are doing.

This class is meant for the bread-and-butter of many web applications -- enable a user to find, view and edit a particular object. Why keep writing these parts again and again? And if you have more extensive needs, it is very easy to still let this class do most of the work and you can concentrate on the differences, making more maintainable code and more sane programmers.

We break the process down into tasks, each task basically corresponding to a particular URL class. (For instance, '/MyApp/show/?myobject_id=4927' is a 'show' task that displays the object with ID 4927.)

Every task allows you to customize an object, means for finding objects or the parameters passed to the template. Each of these methods take two arguments -- the first argument is always the class, and the second is either the information (object, search criteria) to be modified or a hashref of template parameters. (More detail below.)

In this documentation, we first list all the available tasks with a brief description of what they do. Note that these are tasks implemented for you, you are always free to create your own.

Next, we go into depth for each task and describe how you configure it and how you can customize its behavior.


This class supplies the following methods for direct use as tasks. If you override one, you need to supply content. You can, of course, add your own methods (e.g., a 'summary()' method which displays the object information in static detail along with related objects).

  • search_form(): Display a search form.

  • search(): Execute a search and display results.

  • create(): Alias for show() that displays an entry form for a single record.

  • show(): Display a single record.

  • edit(): Modify a single record.

  • remove(): Remove a single record.

  • notify(): Email one or more objects in human-readable format.

  • wizard(): Start the search wizard (generally display a search criteria page).

  • wizard_search(): Run the search wizard and display the results.


  • Template Customizations

    These methods allow you to step in and modify any template parameters that you like.

    You can modify the template that any of these will use by setting the parameter 'template_name'. If you set the template name yourself you need to set it to a fully-qualified name, such as 'mypackage::mytemplate'.

  • Data Customizations

    These methods allow you to step in and modify the data being displayed or processed. Read up on the specific customization method for the exact parameters you can change and what is available to you.


These are configuration and customization items that are not specific to a particular task.



Name of this package.


Object type (e.g., 'user', 'news', etc.)

MY_HANDLER_PATH() ($) (optional)

Path of handler.

Default: '/' . MY_OBJECT_TYPE

MY_OBJECT_CLASS() ($) (optional)

Object class.

Default: Gets object class from $R using MY_OBJECT_TYPE:


_fetch_object_customize( $object )

Called just before an object is returned via fetch_object(). You have the option of looking at $object and making any necessary modifications.

Note that fetch_object() is not called when returning objects from a search, only when manipulating a single object with show(), edit() or remove().



MY_ALLOW_SEARCH_FORM() (bool) (optional)

Should the search form be viewed?

Default: true

MY_SEARCH_FORM_TITLE() ($) (optional)

Set the title for the search form.

Default: 'Search for Thingies'

MY_SEARCH_FORM_TEMPLATE() ($) (optional)

Name of the search form template.

Default: MY_PACKAGE . '::search_form'


_search_form_customize( \%template_params )

Template customization. Typically there are no parameters to set/manipulate except possibly 'error_msg' or 'status_msg' if called from other methods.



MY_ALLOW_SEARCH() (bool) (optional)

Should searches be allowed?

Default: true

MY_SEARCH_FAIL_TASK() ($) (optional)

Task to run if your search fails. The parameter 'error_msg' will be set to an appropriate message which you can display.

Default: search_form

MY_SEARCH_RESULTS_CAP() ($) (optional)

Constrains the max number of records returned. If this is set we run a 'count(*)' query using the search criteria before running the search. If the result is greater than the number set here, we call MY_SEARCH_RESULTS_CAP_FAIL_TASK with an error message set in the 'error_msg' parameter about the number of records that would have been returned.

Note that this is a somewhat crude measure of the records returned because it does not take into account security checks. That is, a search that returns 500 records from the database could conceivably return only 100 records after security checks. Keep this in mind when setting the value.

Default: 0 (no cap)


Task to run in this class when a search exceeds the figure set in MY_SEARCH_RESULTS_CAP. The task is run with a relevant message in the 'error_msg' parameter.

Default: search_form

MY_SEARCH_RESULTS_PAGED() (bool) (optional)

Set to a true value to enable paged results, meaning that search results will come back in groups of MY_SEARCH_RESULTS_PAGE_SIZE. We use the methods in 'results_manage' to accomplish this.

Note: If your objects are not retrievable through a single ID field, you will not be able to page your results automatically. You should be able to do this by hand in the future.

Default: false.


If MY_SEARCH_RESULTS_PAGED is true this is the parameter we will check to see what page number of the results the user is requesting.

Default: 'pagenum'.


If MY_SEARCH_RESULTS_PAGED is set to a true value we output pages of this size.

Default: 50

MY_SEARCH_RESULTS_KEY() ($) (optional)

If MY_SEARCH_RESULTS_PAGED is true this routine will generate a key under which you will save the ID to get your persisted search results. We make the search ID accessible in the template parameters under this name as well as 'search_id'.

Default: MY_OBJECT_CLASS() . '_search_id'

MY_SEARCH_RESULTS_TITLE() ($) (optional)

Title of search results page.

Default: 'Search Results'


Search results template name.

Default: 'search_results'

MY_SEARCH_FIELDS() (@) (optional)

List of fields used to build search. This can include fields from other tables. Fields from other tables must be fully-qualified with the table name.

For instance, for a list of fields used to find users, I might list:

 sub MY_SEARCH_FIELDS { return qw( login_name last_name ) }

Where '' is a field from another table. I would then have to configure MY_SEARCH_TABLE_LINKS (below) to tell CommonHandler how to link my object with that table.

These are the actual parameters from the form used for searching. If the names do not match up, such as if you fully-qualify your names in the configuration but not the search form, then you will not get the criteria you think you will. An obvious symptom of this is running a search and getting many more records than you expected, maybe even all of them.

No default.

MY_SEARCH_FIELDS_EXACT() (@) (optional)

Returns fields from MY_SEARCH_FIELDS that must be an exact match.

This is used in _search_build_where_clause(). If the field being searched is an exact match, we use '=' as a search test.

Otherwise we use 'LIKE' and, if the field is not in MY_SEARCH_FIELDS_LEFT_EXACT or MY_SEARCH_FIELDS_RIGHT_EXACT (see below), wrap the value in '%'.

If you need other custom behavior, do not include the field in MY_SEARCH_FIELDS and use _search_build_where_customize() to set.

No default.


Returns fields from MY_SEARCH_FIELDS that must match exactly on the left-hand side. This sets up:

 $fieldname LIKE "$fieldvalue%"

No default.


Returns fields from MY_SEARCH_FIELDS that must match exactly on the right-hand side. This sets up:

 $fieldname LIKE "%$fieldvalue"

No default.

MY_SEARCH_TABLE_LINKS() (%) (optional)

Returns table name => ID field mapping used to build WHERE clauses that JOIN multiple tables when executing a search.

A key is a table name, and the value enables us to build a join clause to link table specified in the key to the table containing the object being searched. The value is either a scalar or an arrayref.

If a scalar, the value is just the ID field in the destination table that the ID value in the object maps to:

  sub MY_SEARCH_TABLE_LINKS { return ( address => 'user_id' ) }

This means that the table 'address' contains the field 'user_id' which the ID of our object matches.

If the value is an arrayref that means one of two things, depending on the number of elements in the arrayref.

First, a two-element arrayref. This means we are have a non-key field in our object which matches up with a key field in another object.

The elements are:

 0: Fieldname in the object
 1: Fieldname in the other table

(Frequently these are the same, but they do not have to be.)

For instance, say we have a table of people records and a table of phone log records. Each phone log record has a 'person_id' field, but we want to find all the phone log records generated by people who have a last name with 'mith' in it.

     return ( person => [ 'person_id', 'person_id' ] ) }

Which will generate a WHERE clause like:

  WHERE person.last_name LIKE '%mith%'
    AND phonelog.person_id = person.person_id

Second, a three-element arrayref. This means we are using a linking table to do the join. The values of the arrayref are:

 0: ID field matching the object ID field on the linking table
 1: Name of the linking table
 2: Name of the ID field on the destination table

So you could have the setup:

  user (user_id) <--> user_group (user_id, group_id) <--> group (group_id)


      return ( group => [ 'user_id', 'user_group', 'group_id' ] ) }

And searching for a user by a group name with 'admin' would give:

  WHERE LIKE '%admin%'
    AND group.group_id = user_group.group_id
    AND user_group.user_id = user.user_id

No default.

MY_SEARCH_RESULTS_ORDER() ($) (optional)

An 'ORDER BY' clause used to order your results. The CommonHandler makes sure to include the fields used to order the results in the SELECT statement, since many databases will complain about their absence.

No default.


If you want to pass additional parameters directly to the SPOPS fetch_iterator() call, return them here. For instance, if you want to skip security for a particular search you would create:

 sub MY_SEARCH_ADDITIONAL_PARAMS { return { skip_security => 1 } }

Default: An empty hashref (no parameters)


_search_customize( \%template_params )

Template customization. If you are not using paged results there is only the parameter 'iterator' set. If you use paged results, then there is 'iterator' as well as:

  • page_number_field

  • current_page

  • total_pages

  • total_hits

  • search_id

  • search_results_key

_search_criteria_customize( \%search_criteria )

Data customization. Modify the items in \%search_criteria as necessary. The format is simple: a key is a fully-qualified (table.field) fieldname, and its value is either a scalar or arrayref depending on whether multiple values were passed.

For instance, say we wanted to restrict searches to all objects with an 'active' property of 'yes':

 sub _search_criteria_customize {
    my ( $class, $criteria ) = @_;
    $criteria->{''} = 'yes';

Easy! Other possibilities include selecting objects based on qualities of the user -- say certain objects should only be included in a search if the user is a member of a particular group. Since $R is available to you, it is simple to check whether the user is a member of a group and make necessary modifications.

Note that you must use the fully-qualified 'table.field' format for the criteria key or the criterion will be discarded.

The method should always return the hashref of criteria. Failure to do so will likely retrieve all objects in the database, which is frequently a Bad Thing.

_search_build_where_customize( \@tables, \@where, \@values )

Data customization. Allows you to hand-modify the WHERE clause that will be used for searching. If you override this method, you will be passed three arguments:

  1. \@tables: An arrayref of tables that are used in the WHERE clause -- they become the FROM clause of our search SELECT. If you add a JOIN or other clause that depends on a separate table then be sure to add it here -- otherwise the search will fail mightily.

  2. \@where: An arrayref of operations that will be joined together with 'AND' before being passed to the search() method.

  3. \@values: An arrayref of values that will be plugged into the operations.

This might seem a little confusing, but as usual it is easier to show than tell. For example, we want to allow the user to select a date in a search form and find all items one week after and one week before that date:

 sub _search_build_where_customize {
     my ( $class, $table, $where, $value ) = @_;
     my $R = OpenInteract::Request->instance;
     my $search_date = $class->_read_field_date( 'pivot_date' );
     push @{ $where },
       "( TO_DAYS( ? ) BETWEEN ( TO_DAYS( pivot_date ) + 7 ) " .
       "AND ( TO_DAYS( pivot_date ) - 7 ) )";
     push @{ $value }, $search_date;


This task is just an alias for show(), passing along a true value for both the 'edit' and 'is_new_object' parameters, which show() can inspect to do the right thing.


MY_ALLOW_CREATE() (bool) (optional)

Should shortcut to display a form to create a new object be allowed?

Default: false

MY_OBJECT_CREATE_SECURITY() (security level) (optional)

Security required to create an object -- this should be a constant from SPOPS::Secure






MY_ALLOW_SHOW() (bool) (optional)

Should object display be allowed?

Default: true

MY_SHOW_FAIL_TASK() ($) (optional)

If the display of the object fails -- cannot fetch it, object is not active, etc. -- then what method should we run? Whatever method is run should be able to display the error message (in 'error_msg') so the user knows what happened.

Default: 'search_form'

MY_ACTIVE_CHECK() ($) (optional)

Should we check to see if the object is active before displaying it? If true, the return value from this method should be the field to check for a value of 'yes' or '1'.

If you do not want to check the 'active' status of an object, leave this blank (the default).

Default: undef

MY_OBJECT_FORM_TITLE() ($) (optional)

Title of object editing page.

Default: 'Object Detail'

MY_OBJECT_FORM_TEMPLATE() ($) (optional)

Object form template name.

Default: 'object_form'


_show_customize( \%template_params )

Typically there are only the parameters 'object' and MY_OBJECT_TYPE set to the same value.

Note that this task does not differentiate between displaying an object in an editable form and in a static (non-editable) display. If you want to use this task to do both, you can use this customization to set the template name based on the security status of the object.

For instance:

 sub _show_customize {
     my ( $class, $params ) = @_;
     $params->{template_name} = ( $params->{object}{tmp_security_level} < SEC_LEVEL_WRITE )
                                  ? 'mypkg::static_display' : 'mypkg::form_display';



MY_ALLOW_EDIT() (bool) (optional)

Should edits be allowed?

Default: false

MY_EDIT_RETURN_URL() ($) (optional)

URL to use as return when displaying the 'edit' page. (If you do not define this weird things can happen if users logout from the editing page.)

Default: MY_HANDLER_PATH . '/'

MY_EDIT_FIELDS() (@) (optional)

Fields for CommonHandler to retrieve values from the form and set into the object. You can set other values by hand using _edit_customize().

You can also specify fields to be handled automatically by CommonHandler in MY_EDIT_FIELDS_TOGGLED and MY_EDIT_FIELDS_DATE.

No default.

MY_EDIT_FIELDS_TOGGLED() (@) (optional)

List of fields that are either 'yes' or 'no'. If any true value (as perl defines it) is read in then the value of the field is set to 'yes', otherwise it is set to 'no'.

No default

MY_EDIT_FIELDS_DATE() (@) (optional)

List of fields that are dates. If users are editing raw dates and the field value does not need to be manipulated before entering the database, then just keep such fields in MY_EDIT_FIELDS since they do not need to be treated differently. The default is to read the date from three separate fields, but you can override _read_field_date() for your own needs.

No default

MY_EDIT_FAIL_TASK() ($) (optional)

Specify the task to run when the edit fails for any reason -- except if you specify a different task to run when returning from _edit_customize() with an error.

Default: 'search_form'

MY_EDIT_DISPLAY_TASK() ($) (optional)

Task we should execute after we have edited the record.

Default 'show' (re-displays the form you just edited with a status message)


_edit_customize( $object, \%old_data )

Called just before an object is saved to the datastore. This is most useful to perform any custom data retrieval, data manipulation or validation. Data present in the object before any modifications is passed as a hashref in the second argument.

Return value is a two-element list: the first is the status -- either 'OK' or 'ERROR' as exported by this module. The second is a hashref of options whose contents depend on whether you return 'OK' or 'ERROR'.

If you return 'ERROR', thenthe options specify what to do next. If you return 'OK', then the options get passed to the object save() call, which can be useful if for instance you need to tell SPOPS that a the action is a creation even if it looks like an update.

Example. Data validation might look something like:

 package My::Handler::MyHander;

 use OpenInteract::CommonHandler qw( OK ERROR );

 my %required_label = ( name => 'Name', quest => 'Quest',
                        favorite_color => 'Favorite Color' );

 # ... Override the various configuration routines ...

 sub _edit_customize {
     my ( $class, $object, $old_data ) = @_;
     my @msg = ();
     foreach my $field ( keys %required_label ) {
         if ( $object->{ $field } eq '' or ! defined $object->{ $field } ) {
            push @msg, "$required_label{ $field } is a required field. " .
                       "Please enter data for it.";
     return ( OK, undef ) unless ( scalar @msg );
     return ( ERROR, { error_msg => join( "<br>\n", @msg ),
                       method    => 'show' } );

So if any of the required fields are not filled in, the method returns 'ERROR' and a hashref with the method to execute on error, in this case 'show' to redisplay the same object along with the error message to display.

You can specify an action to execute in one of three ways:

  • method: Calls $method() in the current class.

  • class, method: Calls $class->$method().

  • action: Calls the method and class specified by $action.



MY_ALLOW_REMOVE() (bool) (optional)

Should removals be allowed?

Default: false

MY_REMOVE_FAIL_TASK() ($) (optional)

Task to run if the remove fails for any reason.

Default: 'search_form'

MY_REMOVE_DISPLAY_TASK() ($) (optional)

Task to run after the remove completes

Default: 'search_form'


_remove_customize( $object )

Called just before an object is removed from the datastore.



MY_ALLOW_NOTIFY() (bool) (optional)

Should notify requests be fulfilled?

Default: false

MY_NOTIFY_FROM ($) (optional)

Address from which the message should come.

Default: 'admin_email' value from server configuration (see OpenInteract::SPOPS for more info).

MY_NOTIFY_ID_FIELD() ($) (optional)

Specify the field used to grab ID values for objects to notify.

Default: MY_OBJECT_CLASS()->id_field();

MY_NOTIFY_EMAIL_FIELD() ($) (optional)

Specify the field used for the address to which the notification should be sent.

Default: 'email'

MY_NOTIFY_NOTES_FIELD() ($) (optional)

Specify the field used for notes that will be sent along with the notification.

Default: 'notes'

MY_NOTIFY_SUBJECT() ($) (optional)

Subject of email to be sent out.

Default: "Object notification: $num_objects objects in mail"


_notify_customize( \%params )

Data customization. The \%params hashref has the following keys you can modify. All keys/values get sent on to the notify() method of OpenInteract::SPOPS:

  • from_email: Address message is from (MY_NOTIFY_FROM)

  • email: Address message is to (value in MY_NOTIFY_EMAIL_FIELD)

  • subject: Subject of message (MY_NOTIFY_SUBJECT)

  • object: Object(s) fetched from specified IDs (values in MY_NOTIFY_ID_FIELD)

  • notes: Notes in message (value in MY_NOTIFY_NOTES_FIELD)

  • type: Type of object (MY_OBJECT_TYPE)


This class contains some simple support for search wizards. With such a wizard you can use OpenInteract in conjunction with JavaScript to implement a 'Find...' widget so you can link one object to another easily.


MY_ALLOW_WIZARD() (bool) (optional)

Whether to enable the wizard.

Default: false

MY_WIZARD_FORM_TITLE() ($) (optional)

Title of wizard search form page.

Default: 'Wizard: Search'

MY_WIZARD_FORM_TEMPLATE() ($) (optional)

Name of wizard search form template.

Default: 'wizard_form'


_wizard_form_customize( \%params )

Template customization.



MY_ALLOW_WIZARD() (bool) (optional)

Whether to enable the wizard.

Default: false

MY_WIZARD_RESULTS_MAX() ($) (optional)

Max number of results to return.

Default: 50

MY_WIZARD_RESULTS_TITLE() ($) (optional)

Title of wizard search results page.

Default: 'Wizard: Results'


Name of wizard search results template

Default: 'wizard_results'


_wizard_search_customize( \%params )

Template customization. Customize output of the search results.



Scans the GET/POST for relevant (as specified by MY_SEARCH_FIELDS) search criteria and puts them into a hashref. Multiple values are put into an arrayref, single values into a scalar.

We call _search_criteria_customize() on the criteria just before they are passed back to the caller.

Returns: Hashref of search fields and values entered.

_search_build_where_clause( \%search_criteria )

Builds a WHERE clause suitable for a SQL SELECT statement. It can handle table links with configuration information available in MY_SEARCH_TABLE_LINKS.

Returns: Three-value array: the first value is an arrayref of tables used in the search, including the object table itself; the second value is the actual WHERE clause, the third value is an arrayref of the values used in the WHERE clause.

We call _search_build_where_customize() with the three arrayrefs just before returning them.

_edit_assign_fields( $object )

If you override this method you will have to read all the information from the GET/POST to the object. See below FIELD VALUE BEHAVIOR for useful methods in doing this.

Object Retrieval

fetch_object( $id, [ $id_field, $id_field, ... ] )

This method is slightly different than the rest. It retrieves a particular object for you, given either the ID value in $id or given the ID value found in the first one of $id_field that is defined in the GET/POST.

Returns: This method always returns an object. If it does not return an object it will die(). If an object is not retrieved due to an ID value not being found or a matching object not being found, a new (empty) object is returned.

Depends on:


Field Values

_read_field( $apache_request, $field_name )

Just returns the value of $field_name as read from the GET/POST.

_read_field_toggled( $apache_request, $field_name )

If $field_name is set to a true value, returns 'yes', otherwise returns 'no'.

_read_field_date( $apache_request, $field_name )

By default, reads in the value of $field_name which it assumes to be in the format 'YYYYMMDD' and puts it into 'YYYY-MM-DD' format, which it returns. This is probably the method you will most often override, depending on how you present dates to your users.


None known.


GenericDispatcher items available thru methods

Modify the GenericDispatcher so that things like security information, forbidden methods, etc. are available through class methods we can override. We might hold off on this until we implement the ActionDispatcher -- no reason to modify something we will remove/modify soon anyway...




Copyright (c) 2001-2002, inc.. All rights reserved.

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


Chris Winters <>

1 POD Error

The following errors were encountered while parsing the POD:

Around line 1220:

You forgot a '=back' before '=head1'