Home | Wiki | OI 1.x Docs | OI 2.x Docs |
Configuring OpenInteract - Modify how the server works and objects interact
This document has detailed information on how to configure OpenInteract and SPOPS.
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.)
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:
[section subsection] key = value key2 = value
This would evaluate to:
{ section => { subsection => { key => 'value', key2 => 'value' } } }
[section] key = value key = value2 key2 = value
Which would evaluate to:
{ section => { key => [ 'value', 'value2' ], key2 => 'value' } }
[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', }, ... };
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.
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:
InteractBaseDir /home/httpd/interact ConfigClass OpenInteract::Config::PerlFile ConfigDir conf ConfigFile server.perl RequestClass OpenInteract::Request StashClass OpenInteract::Intranet
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.
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 => { 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 name of the object.
Example. Given the following configuration definition:
$spops = { 'user' => { ..., has_a => { 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 => { 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 name 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 => { 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.
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.
<Data::Dumper
SPOPS::ClassFactory
OpenInteract::Startup
Chris Winters <chris@cwinters.com>