Chapter 10. Development Environments

We’ve already seen some examples of how you can modify RT’s behavior through custom scrips, templates, and actions, but RT can be customized even further. You also may need to work on RT’s core code, either to develop features to submit back to the core developers or to fix bugs that you find.

Enabling RT’s development allows several features that make developing RT and RT extensions much simpler. There are several ways you can do this. You can enable RT’s “developer mode” feature when running the configure script by adding the —with-devel-mode switch.

You also can edit the etc/RT_SiteConfig.pm file to include this line:

    Set($DevelMode => 1);

The —with-devel-mode configuration option simply adds this line to the etc/RT_Config.pm file.

Turning on development mode disables the static_source and use_object_files Mason features, which means that components are re-parsed for every request. This guarantees that changes to components will be seen immediately.

It also enables the use of Module::Refresh, which checks for changes to modules on each new request. Without this, it would be necessary to restart the HTTP server every time a module was changed.

Most of RT’s core classes are designed to allow you to customize their behavior through a mechanism called overlays. You also can use subclasses in some cases, or modules such as Damian Conway’s Hook::LexWrap.

Overlays are the easiest way to make major changes to RT’s core classes. If you look at the RT::User class itself, you will see that it contains surprisingly little code.

Near the end, you’ll see these lines:

    eval "require RT::User_Overlay";
    if ($@ && $@ !~ qr{^Can't locate RT/User_Overlay.pm}) {
        die $@;
    }
     
    eval "require RT::User_Vendor";
    if ($@ && $@ !~ qr{^Can't locate RT/User_Vendor.pm}) {
        die $@;
    }
     
    eval "require RT::User_Local";
    if ($@ && $@ !~ qr{^Can't locate RT/User_Local.pm}) {
        die $@;
    }

This is the core of RT’s overlay mechanism. The RT::User_Overlay file ships with RT itself and contains most of the real core logic for the user class. It is important to note that this module’s package declaration is RT::User.

The RT::User_Vendor module would be created by an operating system distribution like Debian or FreeBSD, and the RT::User_Local module is where per-installation custom code should be placed. Neither of these modules exist in a default RT installation.

This mechanism is somewhat unusual, and you may wonder why RT doesn’t just use subclasses. The first reason is that many of RT’s core database-based classes are auto-generated from the database schema. Additionally, using subclasses would require all classes that use RT::User (for example) to use the appropriate subclass instead, which requires a potentially complex configuration mechanism and could be a bit of a headache.

The downside is that you cannot use SUPER to call the code you’ve overridden. If you need to do that, you will need to use Hook::LexWrap, or assign another name to the original subroutine:

    BEGIN { *SUPER_SomeMethod = \&SomeMethod };
     
    sub SomeMethod {
       my $self = shift;
       # ...some preprocessing...
       $self->SUPER_SomeMethod(@_);
       # ...some postprocessing...
     }

You can use the Hook::LexWrap module to achieve something similar to overriding a method in a subclass. Hook::LexWrap lets you insert pre- and post-call wrappers for a method or function call. Code run before the method can change the arguments being passed in, and code run after can change the return value.

Hook::LexWrap calls the wrapper code with an extra argument at the end of @_. If you set this value in your pre-call code, then the main method and post-call code will not be called. You can use this to short-circuit a method call and force a different return value.

Here’s an example that takes advantage of that to prevent the resolution of a ticket which has never had a reply:

    package RT::Ticket;
     
    use strict;
    use Hook::LexWrap;
     
    wrap 'SetStatus' => pre => \&check_for_replies;
     
    sub check_for_replies {
        my $self = shift;
     
        my $transactions = RT::Transactions->new( $self->CurrentUser );
        $transactions->LimitToTicket( $self->Id );
        $transactions->Limit( FIELD => 'Type',
                              VALUE => 'Correspond',
                            );
     
        unless ( $transactions->Count ) {
            $_[-1] = [0, $self->loc('Ticket cannot be resolved without a reply')];
        }
    }
     
    1;

This code would go in local/lib/RT/Ticket_Local.pm.

The wrapper code short-circuits the call to SetStatus() if the ticket has no replies. This is done simply by setting the value of the last argument to the wrapper. If this argument is an array reference, then Hook::LexWrap will turn it into an array if the original method was called in array context.

If the ticket has replies, then the wrapper does not set this value, so the original method is called in the normal fashion.

When adding new functionality to RT, you may need to implement access controls. RT has an access control system that you can use in your extensions. You also can add new rights to the system if necessary.

To check that a user has a specific right, simply call HasRight() on that user object. This method is usually called with two arguments, Right and Object. Right is simply the name of the right to be checked, such as SuperUser or ModifyTicket. Object is the object the user is trying to access. The method will return a boolean indicating whether the user has the given right.

For example, if we have a queue and want to see if the user can see that queue, we call:

    $user->HasRight( Right => 'SeeQueue', Object => $queue );

Many classes define a convenience method called CurrentUserHasRight(), which simply takes a single unnamed argument, the name of the right. So if we want to know if the current user has the “SeeQueue” right, we call:

    $queue->CurrentUserHasRight('SeeQueue');

Some classes define other rights-related convenience methods. See the POD documentation for individual classes for more details.

Profiling RT code is simple if you use the standalone HTTP server that comes with RT. [18] Profiling requires the following steps:

  1. Start the standlone HTTP server with the Perl profiling flag (-d:DProf).

  2. Run many requests that exercise the same code. This is necessary in order to ensure that the cost of simply starting up the server does not outweigh the cost of the code you want to profile.

  3. Exit cleanly, do not just abort the HTTP server process. This makes sure that the state of the Perl interpreter does not become corrupted.

The last step requires that we add a simple extra top-level Mason component, which we will call exit.html. Place this in local/html under your RT installation directory. The component should look like this:

    % exit;

Of course, this is not something you want in a production installation, since it would let anyone abort a process with a simple HTTP request.

To start the standalone server with profiling turned on, run it as, /path/to/perl -d:DProf /path/to/standlone_httpd.

Then run the same request many times. To exit, simply call the exit.html component, which should be available via a URL of /exit.html.

When the process exits, the Perl profiler leaves behind a file tmon.out. Use the dprofpp script to see the results of the profiling in various formats.

If you have RT’s development mode turned on, you may want to turn it off when profiling, as development mode adds non-trivial overhead to each request that is not present in a production environment.

While it’s always nice when your code runs perfectly the first time you use it, you probably will have to spend some time debugging your RT extensions.

Debugging web applications can be tricky. If you can write a standalone script to exercise a piece of code, you can use Perl’s debugger. Run perldoc perldebug for details.

If your code is dependent on being run in the web environment, then you cannot use the debugger.

RT has a test infrastructure that you should use if you are doing core RT development. This helps ensure that any changes you make do not break existing functionality and adding tests for bug fixes or new features makes it easier for RT’s core developers to integrate patches.

The tests use the standard Perl test harness. Tests are written in standard Perl and grouped into test files. As each test file runs, it prints a message showing the status of that file:

    lib/t/autogen/autogen-RT-GroupMembers_Overlay............ok

This means that all the tests in this file ran correctly. If there are errors, these will be printed separately.

When all the tests have finished running, there will be a summary:

    Failed Test                       Stat Wstat Total Fail  Failed  List of Failed
    -------------------------------------------------------------------------------
    lib/t/regression/02basic_web.t     255 65280     1    0   0.00%  ??
    lib/t/regression/03web_compiliati  255 65280     1    0   0.00%  ??
    lib/t/regression/06mailgateway.t    15  3840    40   15  37.50%  4 7 9 16 18-19
                                                                     21 25-26 28
                                                                     32-33 35 38 40
    lib/t/regression/08web_cf_access.    3   768     4    3  75.00%  2-4
    lib/t/regression/19-rtname.t       255 65280     1    0   0.00%  ??

This shows which test files had problems and which tests failed. If the list of failed tests is simply ??, that means that the test file probably died before generating any output.

RT can be configured to run in a variety of languages and has a lot of support internally for internationalization.

RT has an active community, including folks working on extending RT. If you want to participate, the best places to start are the RT wiki at http://wiki.bestpractical.com/ and the RT email lists. See http://www.bestpractical.com/rt/lists.html for more information on the RT email lists.

If you have a question about developing an RT extension, the rt-devel list is the best place to ask.

If you’ve created an extension that you think would be generally useful for the RT community, you can package it and distribute it as a Perl module on CPAN, the Comprehensive Perl Archive Network.

All RT extensions placed on the CPAN should have a name starting with RTx to distinguish them from core RT code.