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

NAME

OpenInteract2::Action - Represent and dispatch actions

SYNOPSIS

 # Define an action in configuration to have its content generated by
 # the TT generator (Template Toolkit) and security
 # checked. (Previously you had to subclass SPOPS::Secure.)
 
 [news]
 class             = OpenInteract2::Action::News
 is_secure         = yes
 content_generator = TT
 
 # The tasks 'listing', 'latest' and 'display' can be cached, for 90
 # seconds, 10 minutes, and 1 day, respectively.
 
 [news cache_expire]
 listing           = 90
 latest            = 10m
 display           = 1d
 
 # Cached content depends on these parameters (multiple ok)
 
 [news cache_param]
 listing           = num_items
 listing           = language
 latest            = num_items
 display           = news_id
 
 # You can declare security levels in the action configuration, or you
 # can override the method _find_security_level()
 
 [news security]
 default           = write
 display           = read
 listing           = read
 latest            = read
 
 # Same handler class, but mapped to a different action and with an
 # extra parameter, and the 'edit' and 'remove' tasks are marked as
 # invalid.
 
 [newsuk]
 class             = OpenInteract2::Action::News
 is_secure         = no
 news_from         = uk
 content_generator = TT
 task_invalid      = edit
 task_invalid      = remove
 
 [newsuk cache_expire]
 listing           = 10m
 latest            = 300
 
 # Future: Use the same code to generate a SOAP response; at server
 # startup this should setup SOAP::Lite to respond to a request at
 # the URL '/SoapNews'.
 
 [news_rpc]
 class             = OpenInteract2::Action::News
 is_secure         = yes
 content_generator = SOAP
 url               = SoapNews
 
 [news_rpc cache_expire]
 listing           = 10m
 latest            = 300
 
 [news_rpc security]
 default           = write
 display           = read
 
 # Dispatch a request to the action by looking up the action in the
 # OpenInteract2::Context object:
 
 # ...using the default task
 my $action = CTX->lookup_action( 'news' );
 return $action->execute;
 
 # ...specifying a task
 my $action = CTX->lookup_action( 'news' );
 $action->task( 'display' );
 return $action->execute;
 
 # ...specifying a task and passing parameters
 my $action = CTX->lookup_action( 'news' );
 $action->task( 'display' );
 $action->param( news => $news );
 $action->param( grafs => 3 );
 return $action->execute;
 
 # Dispatch a request to the action by manually creating an action
 # object
 
 # ...using the default task
 my $action = OpenInteract2::Action->new( 'news' );
 
 # ...specifying a task
 my $action = OpenInteract2::Action->new( 'news', { task => 'display' } );
 
 # ...specifying a task and passing parameters
 my $action = OpenInteract2::Action->new( 'news',
                                         { task  => 'display',
                                           news  => $news,
                                           grafs => 3 } );
 
 # Set parameters after the action has been created
 $action->param( news  => $news );
 $action->param( grafs => 3 );
 
 # Run the action and return the content
 return $action->execute;

 # IN AN ACTION
 
 sub change_some_object {
     my ( $self ) = @_;
     # ... do the changes ...
 
     # Clear out cache entries for this action so we don't have stale
     # content being served up
 
     $self->clear_cache;
 }

DESCRIPTION

The Action object is a core piece of the OpenInteract framework. Every component in the system and part of an application is represented by an action. An action always returns content from its primary interface, the execute() method. This content can be built by the action directly, constructed by passing parameters to a content generator, or passed off to another action for generation. (See "GENERATING CONTENT FOR ACTION" below.)

Action Class Initialization

When OpenInteract starts up it will call init_at_startup() on every configured action class. (The class OpenInteract2::Setup::InitializeActions actually does this.) This is useful for reading static (or rarely changing) information once and caching the results. Since the OpenInteract2::Context object is guaranteed to have been created when this is called you can grab a database handle and slurp all the lookup entries from a table into a lexical data structure.

Here is an example:

 use Log::Log4perl            qw( get_logger );
 use OpenInteract2::Context   qw( CTX );
 
 # Publishers don't change very often, so keep them local so we don't
 # have to fetch every time
 
 my %publishers = ();
 
 ...
 
 sub init_at_startup {
     my ( $class ) = @_;
     $log ||= get_logger( LOG_APP );
     my $publisher_list = eval {
         CTX->lookup_object( 'publisher' )->fetch_group()
     };
     if ( $@ ) {
         $log->error( "Failed to fetch publishers at startup: $@" );
     }
     else {
         foreach my $publisher ( @{ $publisher_list } ) {
             $publishers{ $publisher->name } = $publisher;
         }
     }
 }

Action Tasks

Each action can be viewed as an associated collection of tasks. Generally, each task maps to a subroutine in the package of the action. For instance, the following package defines three tasks that all operate on 'news' objects:

 package My::News;
 
 use strict;
 use base qw( OpenInteract2::Action );
 
 sub latest  { return "Lots of news in the last week" }
 sub display { return "This is the display task!" }
 sub add     { return "Adding..." }
 
 1;

Here is how you would call them, assuming that this action is mapped to the 'news' key:

 my $action = CTX->lookup_action( 'news' );
 $action->task( 'latest' );
 print $action->execute;
 # Lots of news in the last week
 
 $action->task( 'display' );
 print $action->execute;
 # This is the display task!
 
 $action->task( 'add' );
 print $action->execute;
 # Adding...

You can also create your own dispatcher by defining the method 'handler' in your action class. For instance:

TODO: This won't work, will it? Won't we just keep calling 'handler' again and again?

 package My::News;
 
 use strict;
 use base qw( OpenInteract2::Action );
 
 sub handler {
     my ( $self ) = @_;
     my $task = $self->task;
     my $language = CTX->user->language;
     my ( $new_task );
     if ( $task eq 'list' and $language eq 'es' ) {
         $new_task = 'list_spanish';
     }
     elsif ( $task eq 'list' and $language eq 'ru' ) {
         $new_task = 'list_russian';
     }
     elsif ( $task eq 'list' ) {
         $new_task = 'list_english';
     }
     else {
         $new_task = $task;
     }
     return $self->execute({ task => $new_task });
 }
 
 sub list_spanish { return "Lots of spanish news in the last week" }
 sub list_russian { return "Lots of russian news in the last week" }
 sub list_english { return "Lots of english news in the last week" }
 sub display { return "This is the display task!" }
 sub edit { return "Editing..." }
 
 1;

You have control over whether a subroutine in your action class is exposed as a task. The following tasks will never be run:

  • Tasks beginning with an underscore.

  • Tasks listed in the task_invalid property.

Additionally, if you have defined the task_valid property then only those tasks will be valid. All others will be forbidden.

To use our example above, assume we have configured the action with the following:

 [news]
 class        = OpenInteract2::Action::News
 task_valid   = latest
 task_valid   = display

Then the 'add' task will not be valid. You could also explicitly forbid the 'add' task from being executed with:

 [news]
 class        = OpenInteract2::Action::News
 task_invalid = add

See discussion of _find_task() and _check_task_validity() for more information.

Action Types

An action type implements one or more public methods in a sufficiently generic fashion as to be applicable to different applications. Actions implemented using action types normally do not need any code: the action type relies on configuration information and/or parameters to perform its functions.

To use an action type, you just need to specify it in your configuration:

 [foo]
 action_type  = lookup
 ...

Each action type has configuration entries it uses. Here's what the full declaration for a lookup action might be:

 [foo]
 action_type  = lookup
 object_key   = foo
 title        = Foo Listing
 field_list   = foo
 field_list   = bar
 label_list   = A Foo
 label_list   = A Bar
 size_list    = 25
 size_list    = 10
 order        = foo
 url_none     = yes

Action types are declared in the server configuration under the 'action_types' key. OI2 ships with:

 [action_types]
 template_only = OpenInteract2::Action::TemplateOnly
 lookup        = OpenInteract2::Action::LookupEdit

If you'd like to add your own type you just need to add the name and class to the list. It will be picked up at the next server start. You can also add them programmatically using register_factory_type() (inherited from Class::Factory):

 OpenInteract2::Action->register_factory_type( mytype => 'My::Action::Type' );

Action Properties vs. Parameters

Action Properties are found in every action. These represent standard information about the action: name, task, security information, etc. All properties are described in PROPERTIES.

Action Parameters are extra information attached to the action. These are analogous in OpenInteract 1.x to the hashref passed into a handler as the second argument. For instance:

 # OpenInteract 1.x
 
 return $class->display({ object     => $foo,
                          error_msg  => $error_msg,
                          status_msg => $status_msg });
 
 sub display {
     my ( $class, $params ) = @_;
     if ( $params->{error_msg} ) {
         return $R->template->handler( {}, $params,
                                       { name => 'mypkg::error_page' } );
     }
 }

 # OpenInteract 2.x
 
 $action->task( 'display' );
 $action->param( object => $foo );
 $action->param_add( error_msg => $error_msg );
 $action->param_add( status_msg => $status_msg );
 return $action->execute;
 
 # also: assign parameters in one call
 
 $action->task( 'display' );
 $action->param_assign({ object     => $foo,
                         error_msg  => $error_msg,
                         status_msg => $status_msg });
 return $action->execute;
 
 # also: pass parameters in last statement
 
 $action->task( 'display' );
 return $action->execute({ object     => $foo,
                           error_msg  => $error_msg,
                           status_msg => $status_msg });
 
 # also: pass parameters plus a property in last statement
 
 return $action->execute({ object     => $foo,
                           error_msg  => $error_msg,
                           status_msg => $status_msg,
                           task       => 'display' });
 
 sub display {
     my ( $self ) = @_;
     if ( $self->param( 'error_msg' ) ) {
         return $self->generate_content(
                              {}, { name => 'mypkg::error_page' } );
     }
 }

OBSERVABLE ACTIONS

What does it mean?

All actions are observable. This means that any number of classes, objects or subroutines can register themselves with an action class (or a specific action instance) and be activated when that action publishes a notification. It is a great way to decouple an object from other functions that want to operate on the results of that object. The observed object (in this case, the action) does not know how many observers there are, or even if any exist at all.

Observable Scenario

That is all very abstract, so here is a scenario:

Existing action: Register a new user

Notification published: When new user confirms registration.

Desired outcome: Add the user name and email address to various services within the website network. This is done via an asynchronous message published to each site in the network. The network names are stored in a server configuration variable 'network_queue_server'.

How to implement:

 package OpenInteract2::Observer::NewUserPublish;
 
 use strict;
 
 sub update {
     my ( $class, $action, $notify_type, $user ) = @_;
     if ( $notify_type eq 'register-confirm' ) {
         my $user = $action->param( 'user' );
         my $network_servers = CTX->server_config->{network_queue_server};
         foreach my $server_name ( @{ $network_servers } ) {
             my $server = CTX->queue_connect( $server_name );
             $server->publish( 'new user', $user );
         }
     }
 }

You would register this observer in $WEBSITE_DIR/conf/observer.ini like this:

 [observer]
 newuserpublish = OpenInteract2::Observer::NewUserPublish

And the action would notify all observers like this:

 package OpenInteract2::Action::NewUser;
 
 # ... other methods here ...
 
 sub confirm_registration {
     my ( $self ) = @_;
     my $user = create_user_object_somehow( ... );

     # ... check registration ...
     if ( $registration_ok ) {

         # This notifies all observers of the 'register-confirm' event

         $self->notify_observers( 'register-confirm', $user );
         return $self->generate_content(
                        {}, { name => 'base_user::newuser_confirm_ok' } );
     }
 }

In the same observer.ini file you registered the observer you would map the observer to the action (assuming the action is named 'newuser'):

 [observer action]
 newuserpublish = newuser

Finally, in the documentation for the package 'base_user' (since the 'newuser' action lives there), you would have information about what notifications are published by the OpenInteract2::Action::NewUser action so other observers could register themselves.

Built-in Observations

filter

Filters can register themselves as observers and get passed a reference to content. A filter can transform the content in any manner it requires. The observation is posted just before the content is cached, so if the content is cacheable any modifications will become part of the cache. (If you need to filter the cached content watch for the observation 'cache hit'; it also posts a scalar reference of the content.)

Here is an example:

 package OpenInteract2::WikiFilter;
 
 use strict;
 
 sub update {
     my ( $class, $action, $type, $content ) = @_;
     return unless ( $type eq 'filter' );
 
     # Note: $content is a scalar REFERENCE
 
     $class->_transform_wiki_words( $content );
 }

Since a filter is just another type of observer you register them in the same place, $WEBSITE_DIR/conf/observer.ini:

 [observer]
 wiki = OpenInteract2::WikiFilter

And then map the observer to one or more actions:

 [map]
 wiki = news
 wiki = page

See OpenInteract2::Observer for more information.

pre/post common

See the common actions for a number of observations they publish. Generally, the actions fire an observation before they perform their action and after:

OpenInteract2::Action::CommonAdd

Fires: 'pre add' and 'post add'

OpenInteract2::Action::CommonUpdate

Fires: 'pre update' and 'post update'

OpenInteract2::Action::CommonRemove

Fires: 'pre remove' and 'post remove'

MAPPING URL TO ACTION

In OI 1.x the name of an action determined what URL it responded to. This was simple but inflexible. OI 2.x gives you the option of decoupling the name and URL and allowing each action to respond to multiple URLs as well.

The default behavior is to respond to URLs generated from the action name. Unlike OI 1.x it is not strictly case-insensitive. It will respond to URLs formed from:

  • Lowercasing the action name

  • Uppercasing the action name

  • Uppercasing the first letter of the action name, lowercasing the rest.

For example, this action:

 [news]
 class = MyPackage::Action::News

will respond to the following URLs:

 /news/
 /NEWS/
 /News/

This default behavior can be modified and/or replaced by three properties:

  • url: Specify a single URL to which this action will respond. This replaces the default behavior.

  • url_none: Tell OI that this action cannot be accessed via URL, appropriate for box or other template-only actions. This replaces the default behavior.

  • url_alt: Specify a number of additional URLs to which this action will respond. This adds to the default behavior, and may also be used in conjunction with url (but not url_none).

Here are some examples to illustrate:

Use 'url' by itself:

 [news]
 class = MyPackage::Action::News
 url   = News

Responds to:

 /News/

Use 'url' with 'url_alt':

 [news]
 class   = MyPackage::Action::News
 url     = News
 url_alt = Nouvelles
 url_alt = Noticias

Responds to:

 /News/
 /Nouvelles/
 /Noticias/

Use default behavior with 'url_alt':

 [news]
 class   = MyPackage::Action::News
 url_alt = Nouvelles
 url_alt = Noticias

Responds to:

 /news/
 /NEWS/
 /News/
 /Nouvelles/
 /Noticias/

Use 'url_none':

 [news_box]
 class    = MyPackage::Action::News
 method   = box
 url_none = yes

Responds to: nothing

Use 'url_none' with 'url_alt':

 [news_box]
 class    = MyPackage::Action::News
 method   = box
 url_none = yes
 url_alt  = NoticiasBox

Responds to: nothing

The actual mapping of URL to Action is done in the OpenInteract2::Context method action_table(). Whenever the action table is assigned to the context is iterates through the actions, asks each one which URLs it responds to and creates a mapping so the URL can be quickly looked up.

One other thing to note about that context method: it also embeds the primary URL for each action in the information stored in the action table. Since the information is stored in a key that is not a property or parameter the action itself does not care about this. But it is useful to note because when you generate URLs based on an action the first URL is used, as discussed in the examples above.

So, to repeat the examples above, when you have:

 [news]
 class = MyPackage::Action::News
 url   = News

The first URL will be:

 /News/

When you have:

 [news]
 class   = MyPackage::Action::News
 url     = News
 url_alt = Nouvelles
 url_alt = Noticias

The first URL will still be:

 /News/

When you have:

 [news]
 class   = MyPackage::Action::News
 url_alt = Nouvelles
 url_alt = Noticias

The first URL will be:

 /news/

because the default always puts the lowercased entry first.

GENERATING CONTENT FOR ACTION

Actions always return content. That content might be what you expect, it might be an error message, or it might be the result of another action. Normally the content is generated by passing data to some sort of template processor along with the template to use. The template processor passes the data to the template and returns the result. But there is nothing that says you cannot just manually return a string :-)

The template processor is known as a 'content generator', since it does not need to use templates at all. OpenInteract maintains a list of content generators, each of which has a class and method associated with it. (You can grab a content generator from the OpenInteract2::Context object using get_content_generator().)

Generally, your handler can just call generate_content():

 sub display {
     my ( $self ) = @_;
     my $request = CTX->request;
     my $news_id = $request->param( 'news_id' );
     my $news_class = CTX->lookup_object( 'news' );
     my $news = $news_class->fetch( $news_id )
                || $news_class->new();
     my %params = ( news => $news );
     return $self->generate_content(
                         \%params, { name => 'mypkg::error_page' } );
 }

And not care about how the object will get displayed. So this action could be declared in both of the following ways:

 [news]
 class             = OpenInteract2::Action::News
 content_generator = TT
 
 [shownews]
 class             = OpenInteract2::Action::News
 task              = display
 return_parameter  = news
 content_generator = SOAP

If the URL 'http://foo/news/display/?news_id=45' comes in from a browser we will pass the news object to the Template Toolkit generator which will display the news object in some sort of HTML page.

However, if the URL 'http://foo/news/shownews/' comes in via SOAP, with the parameter 'news_id' defined as '45', we will pass the same news object off to the SOAP content generator, which will take the 'news' parameter and place it into a SOAP response.

Caching

Another useful feature that comes from having the content generated in a central location is that your content can be cached transparently. Caching is done entirely in actions but is sizable enough to be documented elsewhere. Please see OpenInteract2::Manual::Caching for the lowdown.

PROPERTIES

You can set any of the properties with a method call. Examples are given for each.

request (object)

TODO: May go away

The OpenInteract2::Request associated with the current request.

response (object)

TODO: May go away

The OpenInteract2::Response associated with the current response.

name ($)

The name of this action. This is normally used to lookup information from the action table.

This property is read-only -- it is set by the constructor when you create a new action, but you cannot change it after the action is created:

Example:

 print "Action name: ", $action->name, "\n";

url ($)

URL used for this action. This is frequently the same as name, but you can override it in the action configuration. Note that this is not the fully qualified URL -- you need the create_url() method for that.

This property is read-only -- it is set by the constructor when you create a new action, but you cannot change it after the action is created:

Setting this property has implications as to what URLs your action will respond to. See "MAPPING URL TO ACTION" for more information.

Example:

 print "You requested ", $action->url, " within the application."

url_none (bool)

Set to 'yes' to tell OI that you do not want this action accessible via a URL. This is often done for boxes and other template-only actions. See "MAPPING URL TO ACTION" for more information.

Example:

 [myaction]
 class    = MyPackage::Action::MyBox
 method   = box
 title    = My Box
 weight   = 5
 url_none = yes

url_alt (\@)

A number of other URLs this action can be accessible by. See "MAPPING URL TO ACTION" for more information.

Example:

 [news]
 class    = MyPackage::Action::News
 url_alt  = Nouvelles
 url_alt  = Noticias

url_additional( \@ or \% )

Action parameter names to associate with additional URL parameters pulled from the request's param_url_additional() method. This association is done in execute().

If specified as an arrayref we associate the parameters no matter what task is called on the action. If specified as a hashref you can specify parameter names per-task, using DEFAULT as a catch-all.

Examples:

 # the value of the first additional URL parameter is assigned to the
 # action parameter 'news_id'
 [news]
 ...
 url_additional = news_id
 
 # Given URL:
 URL: http://foo/news/display/22/
 
 # Task implementation
 sub display {
     my ( $self ) = @_;
     my $id = $self->param( 'news_id' );
     # $id is '22' since we pulled it from the first URL parameter
 }

 # for all actions but 'archive' the value of the first additional URL
 # parameter is assigned to the action parameter 'news_id'; for
 # archive we assign them to 'search_year', 'search_month' and
 # 'search_day'
 [news]
 ...
 [news url_additional]
 DEFAULT = news_id
 archive = search_year
 archive = search_month
 archive = search_day
 
 # Given URL:
 http://foo/news/remove/1099/
 
 # Task implementation matching 'DEFAULT'
 sub remove {
     my ( $self ) = @_;
     my $id = $self->param( 'news_id' );
     # $id is '1099' since we pulled it from the first URL parameter
 }
 
  # Given URL:
 http://foo/news/archive/2005/7/
 
 sub archive {
     my ( $self ) = @_;
     my $year  = $self->param( 'search_year' );
     my $month = $self->param( 'search_month' );
     my $day   = $self->param( 'search_day' );
     # $year = 2005; $month = 7; $day is undef
 }

message_name ($)

Name used to find messages from the OpenInteract2::Request object. Normally you do not need to specify this and the action name is used. But if you have multiple actions pointing to the same code this can be useful

Example:

 [news]
 class        = MyPackage::Action::News
 task_default = latest
 
 [latestnews]
 class        = MyPackage::Action::News
 method       = latest
 message_name = news

action_type ($)

The type of action this is. Action types can provide default tasks, output filters, etc. This is not required.

Example:

 $action->action_type( 'common' );
 $action->action_type( 'directory_handler' );
 $action->action_type( 'template_only' );

See "Action Types" above for how to specify the action types actions can use.

task ($)

What task should this action run? Generally this maps to a subroutine name, but the action can optionally provide its own dispatching mechanism which maps the task in a different manner. (See "Action Tasks" above for more information.)

Example:

 if ( $security_violation ) {
     $action->param( error_msg => "Security violation: $security_violation" );
     $action->task( 'search_form' );
     return $action->execute;
 }

content_generator ($)

Name of a content generator. Your server configuration can have a number of content generators defined; this property should contain the name of one.

Example:

 if ( $action->content_generator eq 'TT' ) {
     print "Content for this action will be generated by the Template Toolkit.";
 }

The property is frequently inherited from the default action, so you may not see it explicitly declared in the action table.

template_source (\%)

You have the option to specify your template source in the configuration. This is required if using multiple content generators for the same subroutine. (Actually, this is not true unless all your content generators can understand the specified template source. This will probably never happen given the sheer variety of templating systems on the planet.)

This will not work when an action superclass requires different parameters to specify content templates. One set of examples are the subclasses OpenInteract2::Action::Common.

Example, not using 'template_source'. First the action configuration:

 [foo]
 class = OpenInteract2::Action::Foo
 content_generator = TT

Now the action:

 sub mytask {
     my ( $self ) = @_;
     my %params = ( foo => 'bar', baz => [ 'this', 'that' ] );
     return $self->generate_content( \%params,
                                     { name => 'foo::mytask_template' } );
 }

Example using 'template_source'. First the configuration:

 [foo]
 class = OpenInteract2::Action::Foo
 content_generator = TT
 ...
 
 [foo template_source]
 mytask = foo::mytask_template

And now the action:

 sub mytask {
     my ( $self ) = @_;
     my %params = ( foo => 'bar', baz => [ 'this', 'that' ] );
     return $self->generate_content( \%params );
 }

What this gives us is the ability to swap out via configuration a separate display mechanism. For instance, I could specify the same class in a different action but use a different content generator:

 [fooprime]
 class = OpenInteract2::Action::Foo
 content_generator = Wimpy
 
 [fooprime template_source]
 mytask = foo::mytask_wimpy_template

So now the following URLs will reference the same code but have the content generated by separate processes:

 /foo/mytask/
 /fooprime/mytask/

You can also specify a message key in place of the template name by using the 'msg:' prefix before the message key:

 [foo template_source]
 mytask = msg:foo.template

This will find the proper template for the current user language, looking in each message file for the key foo.template and using the value there:

 mymsg_en.msg
 foo.template = foo::mytask_template_english

 mymsg_es.msg
 foo.template = foo::mytask_template_spanish

is_secure (bool)

Whether to check security for this action. True is indicated by 'yes', false by 'no' (or anything else).

The return value is not the same as the value set. It returns a true value (1) if the action is secured (if set to 'yes'), a false one (0) if not.

Example:

 if ( $action->is_secure ) {
     my $level = CTX->check_security({ class => ref $action });
     if ( $level < SEC_LEVEL_WRITE ) {
         $action->param_add( error_msg => "Task forbidden due to security" );
         $action->task( 'search_form' );
         return $action->execute;
     }
 }

security_required ($)

If the action is using security, what level is required for the action to successfully execute.

Example:

 if ( $action->is_secure ) {
     my $level = CTX->check_security({ class => ref $action });
     if ( $level < $action->security_required ) {
         $action->param_add( error_msg => "Task forbidden due to security" );
         $action->task( 'search_form' );
         return $action->execute;
     }
 }

(Note: you will never need to do this since the _find_security_level() method does this (and more) for you.)

security_level ($)

This is the security level found or set for this action and task. If you set this beforehand then the action dispatcher will not check it for you:

Example:

 # Action dispatcher will check the security level of the current user
 # for this action when 'execute()' is called.
 
 my $action = OpenInteract2::Action->new({
                    name           => 'bleeble',
                    task           => 'display' });
 return $action->execute;
 
 # Action dispatcher will use the provided level and not perform a
 # lookup for the security level on 'execute()'.
 
 my $action = OpenInteract2::Action->new({
                    name           => 'bleeble',
                    task           => 'display',
                    security_level => SEC_LEVEL_WRITE });
 return $action->execute;

task_valid (\@)

An arrayref of valid tasks for this action.

Example:

 my $ok_tasks = $action->task_valid;
 print "Tasks for this action: ", join( ', ', @{ $ok_tasks } ), "\n";

task_invalid (\@)

An arrayref of invalid tasks for this action. Note that the action dispatcher will never execute a task with a leading underscore (e.g., '_find_records'). This method will not return leading-underscore tasks.

Example:

 my $bad_tasks = $action->task_invalid;
 print "Tasks not allowed for action: ", join( ', ', @{ $bad_tasks } ), "\n";

cache_expire ($ or \%)

Mapping of task name to expiration time for cached data in seconds. You can also use shorthand to specify minutes, hours or days:

 10m == 10 minutes
 3h  == 3 hours
 1d  == 1 day

If you specify a single value it will be used for all tasks within the action. Otherwise you can specify a per-task value using a hashref.

 # default for all actions
 [myaction]
 class = MyPackage::Action::Foo
 cache_expire = 2h
 
 # different values for 'display' and 'listing' tasks
 [myaction]
 class = MyPackage::Action::Foo
 
 [myaction cache_expire]
 display = 2h
 listing = 15m

cache_param (\%)

Mapping of task name to zero or more parameters (action/request) used to identify the cached data. (See OpenInteract2::Manual::Caching)

METHODS

Class Methods

new( [ $name | $action | \%action_info ] [, \%values ] )

Create a new action. This has three flavors:

  1. If passed $name we ask the OpenInteract2::Context to give us the action information for $name. If the action is not found an exception is thrown.

    Any action properties provided in \%values will override the default properties set in the action table. And any items in \%values that are not action properties will be set into the action parameters, also overriding the values from the action table. (See OpenInteract2::ParamContainer.)

  2. If given $action we call clone on it which creates an entirely new action. Then we call init() on the new object and return it. (TODO: is init() redundant with a clone-type operation?)

    Any values provided in \%properties will override the properties from the $action. Likewise, any parameters from \%properties will override the parameters from the $action.

  3. If given \%action_info we create a new action of the type found in the 'class' key and assign the properties and paramters from the hashref to the action. We also do a 'require' on the given class to ensure it's available.

    Any values provided in \%properties will override the properties from \%action_info. Likewise, any parameters from \%properties will override the parameters from the \%action_info. It's kind of beside the point since you can just pass them all in the first argument, but whatever floats your boat.

Returns: A new action object; throws an exception if $name is provided but not found in the Action Table.

Examples:

 # Create a new action of type 'news', set the task and execute
 
 my $action = OpenInteract2::Action->new( 'news' );
 $action->task( 'display' );
 $action->execute;
 
 # $new_action and $action are equivalent...
 
 my $new_action =
     OpenInteract2::Action->new( $action );

 # ...and this does not affect $action at all
 
 $new_action->task( 'list' );
 
 my $action = OpenInteract2::Action->new( 'news' );
 $action->task( 'display' );
 $action->param( soda => 'coke' );
 
 # $new_action and $action are equivalent except for the 'soda'
 # parameter and the 'task' property
 
 my $new_action =
     OpenInteract2::Action->new( $action, { soda => 'mr. pibb',
                                            task => 'list' } );
 
 # Create a new type of action on the fly
 # TODO: will this work?
 
 my $action = OpenInteract2::Action->new({
         name         => 'foo',
         class        => 'OpenInteract2::Action::FooAction',
         task_default => 'drink',
         soda         => 'Jolt',
 });

Object Methods

init()

This method allows action subclasses to perform any additional initialization required. Note that before this method is called from new() all of the properties and parameters from new() have been set into the object whether you have created it using a name or by cloning another action.

If you define this you must call SUPER::init() so that all parent classes have a chance to perform initialization as well.

Returns: The action object, or undef if initialization failed.

Example:

 package OpenInteract2::Action::MyAction;
 
 use base qw( OpenInteract2::Action );
 
 my %DEFAULTS = ( foo => 'bar', baz => 'quux' );
 sub init {
     my ( $self ) = @_;
     while ( my ( $key, $value ) = each %DEFAULTS ) {
         unless ( $self->param( $key ) ) {
             $self->param( $key, $value );
         }
     }
     return $self->SUPER::init();
 }

clone()

For now this is pretty simplistic: create an empty action object using the same class as then given object (call it $action) and fill it with the properties and parameters from $action.

Returns: new action object of the same class as $action

create_url( \%params )

Generate a self-referencing URL to this action, using \%params as an appended query string. Under the covers we use OpenInteract2::URL to do the real work.

Note that you can also override the task set in the current action using the 'TASK' parameter. So you could be on the form display for a particular object and generate a URL for the removal task by passing 'remove' in the 'TASK' parameter.

See "MAPPING URL TO ACTION" for a discussion of how an action is mapped to multiple URLs and which URL will be chosen as the base for the URL generated by this method.

Returns: URL for this action

Examples:

 my $action = OpenInteract2::Action->new({
     name => 'games',
     task => 'explore',
 });
 my $url = $action->create_url;
 # $url: "/games/explore/"
 my $url = $action->create_url({ edit => 'yes' });
 # $url: "/games/explore/?edit=yes"
 my $url = $action->create_url({ TASK => 'edit', game_id => 42 });
 # $url: "/games/edit/?game_id=42"
 
 <a href="[% action.create_url( edit = 'yes' ) %]">Click me!</a>
 # <a href="/games/explore/?edit=yes">Click me!</a>
 <a href="[% action.create_url( task = 'EDIT', game_id = 42 ) %]">Click me!</a>
 # <a href="/games/edit/?game_id=42">Click me!</a>
 
 CTX->assign_deploy_url( '/Archives' );
 my $url = $action->create_url;
 # $url: "/Archives/games/explore/"
 my $url = $action->create_url({ edit => 'yes' });
 # $url: "/Archives/games/explore/?edit=yes"
 my $url = $action->create_url({ TASK => 'edit', game_id => 42 });
 # $url: "/Archives/games/edit/?game_id=42"
 
 <a href="[% action.create_url( edit = 'yes' ) %]">Click me!</a>
 # <a href="/Archives/games/explore/?edit=yes">Click me!</a>
 <a href="[% action.create_url( task = 'EDIT', game_id = 42 ) %]">Click me!</a>
 # <a href="/Archives/games/edit/?game_id=42">Click me!</a>

get_dispatch_urls

Retrieve an arrayref of the URLs this action is dispatched under. This may be an empty arrayref if the action is not URL-accessible.

This is normally only called at OpenInteract2::Context startup when it reads in the actions from all the packages, but it might be informative elsewhere as well. (For instance, we use it in the management task 'list_actions' to show all the URLs each action responds to.) See "MAPPING URL TO ACTION" for how the method works.

Returns: arrayref of URLs this action is dispatched under.

Example:

 my $urls = $action->get_dispatch_urls;
 print "This action is available under the following URLs: \n";
 foreach my $url ( @{ $urls } ) {
     print " *  $url\n";
 }

Object Execution Methods

execute( \%vars )

Generate content for this action and task. If the task has an error it can generate error content and die with it; it can also just die with an error message, but that is not very helpful to your users.

The \%vars argument will set properties and parameters (via property_assign() and param_assign()) before generating the content.

Most actions do not implement this method, instead implementing a task and using the base class implementation of execute() to:

  • lookup the task

  • perform the necessary security checks

  • match up additional URL parameters from the request to action parameters

  • check the cache for matching content (More about caching in OpenInteract2::Manual::Caching.)

  • after the content has been generated, store the content in the cache as necessary

Returns: content generated by the action

forward( $new_action )

TODO: may get rid of this

Forwards execution to $new_action.

Returns: content generated by calling execute() on $new_action.

Examples:

 sub edit {
     my ( $self ) = @_;
     # ... do edit ...
     my $list_action = CTX->lookup_action( 'object_list' );
     return $self->forward( $list_action );
 }

clear_cache()

Most caching is handled for you using configuration declarations and callbacks in execute(). The one part that cannot be easily specified is when objects change. If your action is using caching then you will probably need to call clear_cache() whenever you modify objects whose content may be cached. "Probably" because your app may not care that some stale data is served up for a little while.

For instance, if you are caching the latest news items and add a new one you do not want your 'latest' listing to miss the entry you just added. So you clear out the old cache entries and let them get rebuilt on demand.

Since we do not want to create a crazy dependency graph of data that is eventually going to expire anyway, we just remove all cache entries generated by this class.

Returns: number of cache entries removed

Object Content Methods

generate_content( \%content_params, [ \%template_source ], [ \%template_params ] )

This is used to generate content for an action.

The information in \%template_source is only optional if you have specified the source in your action configuration. See the docs for property template_source for more information.

Also, note that any view messages you have added via view_messages() or add_view_message() will be passed to the template in the key action_messages.

TODO: fill in more: how to id content

Object Property and Parameter Methods

See OpenInteract2::ParamContainer for discussion of the param(), param_add(), param_clear() and param_assign() methods.

property_assign( \%properties )

Assigns values from properties specified in \%properties. Only the valid properties for actions will be set, everything else will be skipped.

Currently we only set properties for which there is a defined value in \%properties.

Returns: action object ($self)

See PROPERTIES for the list of properties in each action.

property( [ $name, $value ] )

Get/set action properties. (In addition to direct method call, see below.) This can be called in three ways:

 my $props   = $action->property;            # $props is hashref
 my $value   = $action->property( $name );   # $value is any type of scalar
 $new_value  = $action->property( $name, $new_value );

Returns: if called without arguments, returns a copy of the hashref of properties attached to the action (changes made to the hashref will not affect the action); if called with one or two arguments, returns the new value of the property $name.

Note that this performs the same action as the direct method call with the property name:

 # Same
 $action->property( 'task_invalid' );
 $action->task_invalid();
 
 # Same
 $action->property( task_invalid => [ 'foo' ] );
 $action->task_invalid( [ 'foo' ] );

See PROPERTIES for the list of properties in each action.

property_clear( $key )

Sets the property defined by $key to undef. This is the only way to unset a property.

Returns: value previously set for the property $key.

See PROPERTIES for the list of properties in each action.

property_info()

Get a hash of all property names and descriptions -- used in a management task so you can easily lookup properties without jumping into the (fairly long) docs.

param_from_request( @param_names )

Sets the action parameter value to the request parameter value for each name in @param_names.

This will overwrite existing action parameters if they are not already defined.

Returns: nothing

add_error( @msg )

Adds message (joined msg) to parameter 'error_msg').

Returns: added message

add_status( @msg )

Adds message (joined msg) to parameter 'status_msg').

Returns: added message

add_error_key( $key, [ @msg_params ] )

Adds error message (under param name 'error_msg') using the resource key $key which may also optionally need @msg_params.

Returns: added message

add_status_key( $key, [ @msg_params ] )

Adds status message (under param name 'status_msg') using the resource key $key which may also optionally need @msg_params.

Returns: added message

clear_error()

Removes all error messages.

clear_status()

Removes all status messages.

message_from_key_or_param( $param_name, $message_key, @key_arguments )

Shortcut for returning a message from either the localized message store or from the given parameter. For instance, you might have an action configured:

 [myaction]
 title = This is a generic title
 title_key = mypkg.myaction.title

If you call:

 my $msg = $myaction->message_from_key_or_param( 'title', 'title_key' );

The $msg variable should have whatever is in the localization table for 'mypkg.myaction.title'. If 'title_key' wasn't defined the method would return 'This is a generic title'.

Returns: message from localization tables or from the action parameter

view_messages( [ \%messages ] )

Returns the message names and associated messages in this action. These may have been set directly or they may have been deposited in the request (see action_messages() in OpenInteract2::Request) and picked up at action instantiation.

Note that these get put in the template content variable hashref under the key action_messages as long as the content is generated using generate_content().

Returns: hashref of view errors associated with this action; may be an empty hashref.

add_view_message( $msg_name, $msg )

Assign the view messgate $msg_name as $msg in this action.

Internal Object Execution Methods

You should only need to know about these methods if you are creating your own action.

_msg( $key, @args )

Shortcut to creating a localized message. Under the hood this calls:

 CTX->request->language_handle->maketext( $key, @args );

Example:

 if ( $@ ) {
     $action->param_add(
         error_msg => $action->_msg( 'my.error.message', "$@" )
     );
 }

_find_task()

Tries to find a task for the action. In order, the method looks:

  • In the 'method' property of the action. This means the action is hardwired to a particular method and cannot be changed, even if you set 'task' manually.

    TODO: This might change... why use 'method' when we could keep with the task terminology and use something like 'task_concrete' or 'task_only'?

  • In the 'task' property of the action: it might already be defined!

  • In the 'task_default' property of the action.

If a task is not found we throw an exception.

Returns: name of task.

_check_task_validity()

Ensure that task assigned is valid. If it is not we throw an OpenInteract2::Exception.

A valid task:

  • does not begin with an underscore.

  • is not listed in the task_invalid property.

  • is listed in the task_valid property if that property is defined.

Returns: nothing, throwing an exception if the check fails.

_find_task_method()

Finds a valid method to call for the action task. If the method handler() is defined in the action class or any of its parents, that is called. Otherwise we check to see if the method $task() -- which should already have been checked for validity -- is defined in the action class or any of its parents. If neither is found we throw an exception.

You are currently not allowed to have a task of the same name as one of the action properties. If you try to execute a task by this name you will get a message in the error log to this effect.

Note that we cache the returned code reference, so if you do something funky with the symbol table or the @ISA for your class after a method has been called, everything will be mucked up.

Returns: code reference to method for task.

_check_security()

Checks security for this action. On failure throws a security exception, on success returns the security level found (also set in the action property security_level). Here are the steps we go through:

  • First we get the security level for this action. If already set (in the security_level property) we use that. Otherwise we call _find_security_level to determine the level. This is set in the action property security_level.

  • If the action is not secured we short-circuit operations and return the security level.

  • Third, we ensure that the action property security contains a hashref. If not we throw an exception.

  • Next, we determine the security level required for this particular task. If neither the task nor 'DEFAULT' is defined in the hashref of security requirements, we assume that SEC_LEVEL_WRITE security is required.

    The level found is set in the action property security_required.

  • Finally, we compare the security_level with the security_required. If the required level is greater we throw a security exception.

Returns: security level for action if security check okay, exception if not.

_find_security_level()

Returns the security level for this combination of action, user and groups. First it looks at the 'is_secure' action property -- if true we continue, otherwise we return SEC_LEVEL_WRITE so the system will allow any user to perform the task.

If the action is secured we find the actual security level for this action and user and return it.

Returns: security level for action given current user and groups.

TO DO

URL handling

How we respond to URLs and the URLs we generate for ourselves is a little confusing. We may want to ensure that when a use requests an alternate URL -- for instance '/Nouvelles/' for '/News/' -- that the URL generated from 'create_url()' also uses '/Nouvelles/'. Currently it does not, since we're using OI2::URL to generate the URL for us and on the method call it's divorced from the action state.

We could get around this with an additional property 'url_requested' (or something) which would only be set in the constructor if the 'REQUEST_URL' is passed in. Then the 'create_url' would use it and call the 'create' method rather than 'create_from_action' method in OI2::URL.

SEE ALSO

OpenInteract2::Context

OpenInteract2::URL

Class::Observable

COPYRIGHT

Copyright (c) 2002-2005 Chris Winters. All rights reserved.

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

AUTHORS

Chris Winters <chris@cwinters.com>