NAME

Configuring OpenInteract - Modify how the server works and objects interact


SYNOPSIS

This document has detailed information on how to configure OpenInteract and SPOPS.


INTRODUCTION

Configuration is one of the primary ways you can influence how OpenInteract functions. You can specify different databases to use, directories, cache information, session management and more.

SPOPS requires that every object you create have a set of metadata that can be used to create the class. Most of the time you don't even need to write any code -- that code is created on-the-fly whenever you start your mod_perl server. (Setting sufficiently high debugging options in SPOPS for use in SPOPS::ClassFactory will show you how much code is created.)


FILE FORMAT

We generally use four formats throughout OpenInteract. If you create new configuration files, try to stick to these naming schemes.

.conf

Typical configuration file format: information separated into key-value pairs, blank lines and lines that begin with a comment (#) are skipped.

Example:

  MyValue    emmet otter
  HerValue   fraggle rock
 TheirValue  jughandle
   # ringo   starr

Parsing this would return a hashref:

 { 
   MyValue => 'emmet otter',
   HerValue => 'fraggle rock',
   TheirValue => 'jughandle'
 }

.dat

Very simple: one item per line. Blank lines and lines beginning with a comment (#) are skipped entirely.

Example:

 MyClass
 HerClass
 TheirClass
 #RingoClass

Parsing this would return an arrayref:

 [ 'MyClass', 'HerClass', 'TheirClass' ]

Note: This will probably be phased out in the future.

.ini

The time-tested and easily-edited INI file format. This represents nested data without allowing configuration information that has an upper-bound on complexity.

If you've never seen it before, it looks like this:

 [section]
 key = value
 key2 = value

This would evaluate to:

 { section => { key => 'value',
                key2 => 'value' } }

OpenInteract comes with its own INI reader implementation which has a few extra features:

multi-level sections
Instead of specifying a single level in the section you can specify two. (You can do more than two as well, but we put an artificial limit so as to prevent configuration complexity.)
 [section subsection]
 key = value
 key2 = value

This would evaluate to:

 { section => { subsection => { key => 'value',
                                key2 => 'value' } } }

multivalued keys
You can set multiple values in a key:
 [section]
 key = value
 key = value2
 key2 = value

Which would evaluate to:

 { section => { key => [ 'value', 'value2' ],
                key2 => 'value' } }

Global values
Entries under the key 'Global' will be saved at the top level of the configuration object:
 [Global]
 DEBUG = 1
 server_name = My Server!

Which would evaluate to:

{ DEBUG => 1, server_name => 'My Server!' }

See OpenInteract::Config::Ini for more information.

.perl

This file is a full-fledged perl data structure, dumped to a file using Data::Dumper. It can be any type of structure, but it's normally used to represent a hashref containing all sorts of different types of information. It's also fairly easy to edit such a file using your favorite plain-text file editor.

When reading this type of configuration, we just return the data structure saved in the file -- if the file is an arrayref, we return an arrayref.

When we use this structure to save information for objects (such as the OpenInteract::Config::PerlFile object), we never save class information about the object, just the data. We can always re-bless the structure after it's eval'd in.

Example:

 $data = {
          'db_info' => {
            'db_owner' => '',
            'username' => 'cwinters',
            'password' => '',
            'dsn' => 'DBI:mysql:database=mysql',
            'db_name' => 'interact',
          },
          ...
 };


OPENINTERACT

There are two primary levels of configuration in OpenInteract: the server and the package. Typically, your application will have its own set of server files and a set of configuration files with each application.

Server-specific Configuration

File: /conf/base.conf

This is one of the most important configuration files in OpenInteract. This file allows OpenInteract::Startup to bootstrap all the configuration information by supplying information for the class that reads in the data for the server configuration (OpenInteract::Config::PerlFile by default), the base directory for this application, the name of the configuration directory and file. You can also specify the Request class (OpenInteract::Request by default) and a Stash class (no default specified -- every application needs to have its own).

Example:

 base_dir         /opt/OpenInteract
 website_dir      /home/httpd/oitest
 website_name     OITest
 config_type      ini
 config_class     OpenInteract::Config::IniFile
 config_dir       conf
 config_file      server.ini
 request_class    OpenInteract::Request
 stash_class      OITest::Stash
 templib_dir      /home/httpd/oitest/tmplib

File: /conf/apache.dat

List the classes needed by mod_perl in the Apache:: class. The only ones you might need to change are Apache::StatINC (shouldn't be used on production servers).

File: /conf/server.ini

This file controls a many fundamental aspects of your application: which directories to use, how to connect to a database, how to configure a cache, etc.

In addition, you can setup aliases to use within your application, calling the alias from $R. (See Description and Contents of $R for more information.) And there is some basic information required by SPOPS located here as well.

When you create a website your server.ini file is initialized with sensible values. It also has quite a bit of explanation about the values there and where they're used. =head2 Package-specific Configuration

File: pkg/$pkg/conf/action.perl

This file contains directives that register modules and components with the server. Each module and component in the system has a unique identifier -- you can use the OpenInteract script scripts/find_modules.pl (note, not yet available - CW) to product a list of current modules and components.

Here is an example of a module.perl file, from the OpenInteract package contact:

 $module = {
            'person'     => {
                'module'    => 'person',
                'class'     => 'OpenInteract::Handler::Person',
            },
            'address'     => {
                'path'      => 'Address',
                'module'    => 'address',
                'class'     => 'OpenInteract::Handler::Address',
                'tool'      => 'contact',
            },
            'contact'     => {
                'path'      => 'Contact',
                'module'    => 'contact',
                'class'     => 'OpenInteract::Handler::Contact',
                'tool'      => 'contact',
            },
           'addresslist' => {
                'class'     => 'OpenInteract::Handler::Address',
                'method'    => 'listing',
                'module'    => 'addresslist',
                'conductor' => 'null',
                'security'  => 'no',
                'tool'      => 'contact',
            },
            ...
 };

The first three list standard modules -- they rely on the second part of the URL to perform an action. For example, calling the URL '/Person/' would simply run the default method in the OpenInteract::Handler::Person class, while calling '/Person/show/' would call the 'show' method of that same class.

The fourth item ('addresslist') specifies a component. (See OpenInteract Guide to Components for more information about what a component is and does.) Briefly, there are two types of components: template-based and code-based. Template-based components do not need to be specified in the pkg/$pkg/conf/module.perl file -- you can simply call them by the name you give the template.

The second type of component, code-based, must be specified in the configuration. This type of component is basically indistinguishable from a module, except for the fact that you cannot call more than one method on it, and that it's meant to be part of a page rather than an entire page. Code-based components wrap class and method names into one easily used call.

The code-based component can use an entirely separate class from other modules, or it can 'poach' a method from a classed used as a normal module. The latter is what we do in the fourth configuration item above. When we call the 'addresslist' component, we're just returning the results of the method call OpenInteract::Handler::Address->listing.

Currently, the module does not list parameters that can be passed to a component, although this might exist in a future version of OpenInteract.

NOTE: We may add a great deal of information in this file in the near future as an effort to enable multiple applications to use the same handlers.

Also, see the entry for pkg/$pkg/conf/spops.perl below.


SPOPS

File: pkg/$pkg/conf/spops.perl

The file pkg/$pkg/conf/spops.perl is extremely important to the operation of OpenInteract. Each package defines the SPOPS objects it uses through this file. A vast majority of these objects have no class files behind them, meaning that when we define a class OpenInteract::DataObject, there is likely no file anywhere in the system titled OpenInteract/DataObject.pm as is traditional with perl objects.

Instead, we take the metadata specified in the pkg/$pkg/conf/spops.perl file and create classes on the fly for the objects when the server is started. These classes include configuration information for the class, an inheritance hierarchy to determine the various behaviors of this class, and methods to deal with inter-relationships of objects.

Here is an example of a file, found in pkg/news/conf/spops.perl:

  $spops = {
      'news' => {
        class        => 'OpenInteract::News',
        isa          => [ qw/ OpenInteract::Linked  OpenInteract::Grouped  
                              OpenInteract::SPOPS::DBI  SPOPS::Secure  
                              SPOPS::DBI::MySQL  SPOPS::DBI / ],
        field        => [ qw/ news_id posted_on posted_by title 
                              news_item active expires_on active_on / ],
        id_field     => 'news_id',
        no_insert    => [ qw/ news_id / ],
        no_update    => [ qw/ news_id posted_on / ],
        skip_undef   => [ qw/ active expires_on / ],
        sql_defaults => [ qw/ active / ],
        key_table    => 'news',
        base_table   => 'news',
        field_alter  => {},
        alias        => [],
        has_a        => { 'OpenInteract::User' => [ 'posted_by' ] },
        links_to     => {},
        creation_security => {
           u   => { level => 'WRITE' },
           g   => undef,
           w   => { level => 'READ'},
        },
        as_string_order => [ qw/ title news_item posted_on / ],
        as_string_label => { title => 'Title', 
                             news_item => 'News Item',
                             posted_on => 'Posted On' },
        track => {
           create => 1, update => 1, remove => 1
        },
        display => { url => '/News/show/', class => 'OpenInteract::Handler::News', method => 'show' },
        linkterm => 1,
        name => sub { return $_[0]->{title} },
        object_name => 'News',
      },
      ...
 };

We'll break this down, field by field, and discuss what each does, the data it expects, and how the data you enter are used.

name ($)

'news' in the example above -- this is the primary alias you will use to access the object class through $R. For instance, using the example above, calling: $R->news would return OpenInteract::News.

Note that you can setup additional aliases for the same class using the alias field of the configuration (more below).

class ($)

Name the class used by this object. Most of the time, you can make up something completely arbitrary -- since there is no code file behind most classes, what you name them doesn't really matter, as long as you always access the class name through $R and the alias method mentioned in name, above.

As we discuss below in PACKAGES AND CONFIGURATION, you can use the fact that most SPOPS classes are created using no code to your advantage. For instance, if you have multiple OpenInteract applications running on the same mod_perl server, you might need to differentiate among different implementations of the same class -- one site needs a OpenInteract::News class that includes full-text searching, another needs the same class without full-text searching. The first can define in its application-specific pkg/news/conf/spops.perl file the following:

  $spops = {
      'news' => {
        class        => 'SiteOne::News',
        isa          => [ qw/ OpenInteract::FullText OpenInteract::Linked  
                              OpenInteract::Grouped  OpenInteract::SPOPS::DBI
                              SPOPS::Secure  SPOPS::DBI::MySQL  
                              SPOPS::DBI / ],
      },
      ...
 };

While the other site can define in its application-specific pkg/news/conf/spops.perl file the following:

  $spops = {
      'news' => {
        class        => 'SiteTwo::News',
        isa          => [ qw/ OpenInteract::Linked  
                              OpenInteract::Grouped  OpenInteract::SPOPS::DBI
                              SPOPS::Secure  SPOPS::DBI::MySQL  
                              SPOPS::DBI / ],
      },
      ...
 };

In the eyes of perl, they are two entirely separate classes since they have different names for 'class'. But they use the same alias -- that is, you can use $R->news->fetch_group( ... )> in both applications when you use a news object -- as well as the same field names, table definitions, etc.

isa (\@)

List the parents of this class. The number of classes a SPOPS class typically inherit from might make traditional object-oriented programmers cringe. However, many of these classes implement only a few public methods. And most are very task-specific -- for intance, the OpenInteract::Linked, OpenInteract::Grouped and OpenInteract::FullText classes listed in the examples above really only exist to create rules within a class. And some of the SPOPS classes (such as SPOPS::DBI::MySQL might implement only one or two methods to deal with a function such as generating primary key values.

See the writeup in class to see how we can manipulate the values in 'isa' to get different behaviors for the same implmentation of a class, just using a different class name.

Note that the order of the 'isa' field is extremely important, just as it is in perl. You generally want to list classes first that have more specific implementations -- for example, listing SPOPS::Secure before any of the SPOPS::DBI classes in the example above, since the former overrides methods in the latter.

field (\@)

List the fields used by this class. Order does not really matter, although it's traditional to put the key field first.

id_field ($)

Name the field used by this class to uniquely identify a record. Currently SPOPS does not support objects identified by multiple key fields.

no_insert (\@)

List the fields that should not be used when creating a new record. For many implementations that use auto-generated primary key values (either through a sequence or auto-incrementing field), you will want to include the 'id_field' here. Another field might be a timestamp field which is generated automatically by the database.

no_update (\@)


List the fields that should not be used when updating an existing
record. Typical fields include the id_field (because you don't want
people manipulating the primary key) or timestamp fields.

skip_undef (\@)

List the fields for which you do not wish to include in new or updated records if they are undefined. For instance, you may have defined a default value for the field 'active' -- all new records default to ``active = 'no''' since you want an administrator to approve the record before making it live.

In some databases, the default value will get put in only if the field and a value are not specified in the INSERT or UPDATE statements. This list allows you to skip over those fields

sql_defaults (\@)

List the fields which have a default value in the database. When a new record is created, we re-fetch the record if there are defaults because we don't want the information in an object to ever be inconsistent with what is in the database. So if, as in the example in skip_undef, you have the default set to 'no' for the field 'active', you want the object to reflect this after you insert it.

key_table ($) (optional, specific to SPOPS::DBI::Keypool)

Name the table to use when retrieving a key from the key pool.

base_table ($)

Name the table used for this class. Do not include any database or ownership information.

field_alter (\%) (optional)

Sometimes you want to retrieve the data in a pre-formatted fashion from the database, such as for date fields. This allows you to tell the database to use database-specific commands to return the same information in a different format.

For example, you could return dates in MySQL in a different format using:

  field_alter  => { 
       posted_on => q/DATE_FORMAT( posted_on, '%a %b %d %h:%i %p' )/,
  },

This command will be passed to the database on any calls to $obj->fetch or $obj->fetch_group and the formatted data returned.

alias (\@) (optional)

List additional aliases that you'd like to use to refer to this class.

has_a (\%) (optional)

List the objects that this object can 'contain' by virtue of having a key field from another object as one of its properties. When the server is started, the SPOPS configuration automatically sets up methods to access this separate object from the original object.

If you only contain one of a particular object, you'll frequently use the same name for the key field as the object you're containing. For example, if you have a OpenInteract::User object in your object, you might use the field 'user_id' to denote the property.

To retrieve an object that is using the same key field name, just refer to the class of the object.

Example. Given the following configuration definition:

 $spops = {
    'user' => {
        ...,
        has_a        => { 'OpenInteract::Theme' => [ 'theme_id' ], },
        ...,
    },
 };

You can retrieve the 'theme' object associated with a particular user by simply calling:

 my $theme_obj = $user_object->theme;

You can also contain more than one of a particular object -- which is why the 'theme' key points to an arrayref in the previous example. A different naming scheme is used for those objects that do not have the same key field name as the object they're associated with.

For these fields, you call the object by appending '_>object_name<' to the name of the property. So if an object has the key field for OpenInteract::User in a field titled 'posted_by', you can retrieve the object associated with that field by calling: $user_object->posted_by_user.

Example. Given the following configuration definition, slightly altered from the previous one:

 $spops = {
    'user' => {
        ...,
        has_a        => { 'OpenInteract::Theme' => [ 'theme_id', 'parent_derived_from' ], },
        ...,
    },
 };

You can make the following calls to get the 'theme' objects.

 my $theme_obj = $user_object->theme;
 my $parent_them_obj = $user_object->parent_derived_from_theme;

links_to (\%) (optional)

This functionality is similar to the has_a field, but instead of containing a single object, an object can be associated with a number of objects. Here we associate this relationship by specifying the class of the related object and the table by which the objects are related.

When we configure the SPOPS objects, we create three methods for each association: <name-of-object>, <name-of_object>_add and <name-of-object>_remove.

Example. Given the following configuration definition:

 $spops = {
    'user' => {
        ...,
        links_to     => { 'OpenInteract::Group' => 'sys_group_user' },
        ...,
    },
 };

We can do the following:


 my $group_obj_list = $user_obj->group;
 my $added = $user->group_add( [ $group_obj, $group_obj, ... ] );
 my $added = $user->group_add( [ $group_id, $group_id, ... ] );
 my $removed = $user->group_remove( [ $group_obj, $group_obj, ... ] );
 my $removed = $user->group_remove( [ $group_id, $group_id, ... ] );

The group_add and group_remove methods in the example don't remove any groups or users, just the links between them.

NOTE: The functionality for this is still being worked on -- in particular, we might differentiate in the future between objects that are linked via a link table (a many-to-many relationship) and objects that are linked via a single field in a separate table (a one-to-many relationship).

We will also figure out a clean way to retrieve a group of associated objects in a particular order. For example:

 my $group_obj_list = $user_obj->group( { order => 'name' } );

Which you cannot do now with objects that are using a link table to hold the associations.

creation_security (\%)

Specify the intial creation security for this object. Currently, this is a pretty blunt instrument -- you specify either a level (e.g., 'WRITE', 'READ', 'NONE' for all three scopes (u == 'SEC_SCOPE_USER', g == 'SEC_SCOPE_GROUP', w == 'SEC_SCOPE_WORLD'). You can also specify a hashref with a key 'code' and list a class and method that will install the security for this object whenever one is created.

This behavior will likely be overrided by Security Policies, which will be administered from a browser interface.

as_string_order (\@) (optional)

List the fields you'd like to use when the as_string() method of the object is called. If you do not specify this and/or the as_string_label keys, SPOPS will create a representation of the object for you.

as_string_label (\%) (optional)

Give the fieldnames labels when outputting the object as a string.

track (\%)

Specify how you want to track modifications made to this object -- a true value for any of 'create', 'update' or 'remove' will log that modification, along with the user who made it and the date and time it was made.

display (\%)

Control how this object is displayed. The most-used field is 'url', which is a base url to which a query string with the ID field and its value is added.

You can also specify 'class' and 'method', for future use of a handler which simply takes a class name and object ID and returns the display for that object.

name (\&)

How can you name each specific object? This should be a code reference that takes the object as a parameter and returns the name for an object -- for instance, for a news story it would be the title. For a record in a contact database it might be the full name of a person.

object_name ($)

What is the generic name for this object? For instance, 'News' for news items, 'Classified ad' for classified objects, etc.


PACKAGES AND CONFIGURATION

When the server reads in the packages and the configuration for each package, it creates the necessary SPOPS classes on the fly. However, if you're running multiple applications on one server or if you want to modify the features of an OpenInteract package, you need to be able to override information specified in the configuration files.

Fortunately, you don't need to do a full copy-and-paste of the original configuration information. You can override specified fields of information in the original configuration by specifying those fields. All other information is inherited from the default.

So in your pkg/$pkg/conf/spops.perl file, you can specify a new 'class' and 'isa' for an object, keeping all the other information intact so that the tables and default data for the objects will still be applicable.


SEE ALSO

<Data::Dumper

SPOPS::ClassFactory

OpenInteract::Startup


AUTHORS

Chris Winters <chris@cwinters.com>