- Overview
I have a bunch of classes that represent email filters, and are backed by a legacy database. The interface to the legacy database is through DBIx::Class
. A filter is essentially two things: a condition and an action. An email message is passed to the condition object, which reports whether the condition is satisfied; if so, the message is passed to the action object, which performs some action, possibly modifying the message. The legacy database principally involves two tables: mh_conds
, representing conditions, and mh_actions
, representing actions.
I have code that examines the tables using DBIx::Class
and constructs Filter
objects, and also code for the reverse direction: you can manufacture a Filter
object, including its subsidiary Condition
and Action
objects, and then store it into the database.
The storage code is peculiar. The way it works is that Condition
and Action
objects each have an as_rows
method, which returns a list of DBIx::Class::Row
objects. To install a filter into the database, the filter manager first calls as_rows
to get the rows, and then calls insert_or_update
on each row.
I know this is weird, and not the expected way to do it. What's the expected way to do it?
Some code samples are attached below. I tried to include only the relevant portions of this medium-sized code base; if something crucial seems missing, please don't hesitate to ask to see it.
- Technical summary
Relevant portions of all the classes discussed in this section are attached here.
Filters are in a Pobox::Filter
hierarchy. A Pobox::Filter::MHLegacy
object conforms to the Filter interface but represents a filter object that was fetched from the legacy database or that will be stored into the legacy database. An MHLegacy
filter contains a condition, of type Pobox::Filter::Condition::MHCondLegacy
, and an action, of type Pobox::Filter::Condition::MHActionLegacy
.
The action is much simpler because actions are represented in the database by single records. The MHActionLegacy
object is essentially just a wrapper around a single database row, represented by a Pobox::Schema::Result::Action
object. This object might previously have been fetched from the database, and possibly modified, or it might have been constructed via Pobox::Schema::Result::Action→new
.
Conditions are more complex, and may contain subconditions, joined by boolean operators, so it is best to think as a condition structure as a tree whose leaves are simple conditions such as "Subject line contains 'Make Money Fast!'". The MHCondLegacy
object represents a complete condition from the legacy database. It contains one or more Pobox::Filter::Condition::MHBlockLegacy
objects, each of which contains one or more Pobox::Filter::Condition::MHRowLegacy
object. Each MHRowLegacy
object is a wrapper around a single database row, represented by a Pobox::Schema::Result::Condition
object. Again, these single-row objects might have been fetched from the database, fetched and then modified, or constructed de novo.
Filters are installed, deleted, and modified by icg2::Account::FilterManager
. When asked to install a filter into the database (say in →save_filter
), the FilterManager
asks the filter for its condition rows and its action rows and then inserts them into the database (in →store_rows
). It does this by looping over the rows and calling →insert_or_update
on each.
When a filter is asked for its action rows (→action_rows
) it delegates the request to its action component via →action→get_rows
. MHActionLegacy::get_rows
simply returns the single Pobox::Schema::Result::Action
object that it encapsulates. Similarly, when a filter is asked for its condition rows it delegates the request to its condition component via →condition→get_rows
. For an MHLegacy
filter, this calls MHCondLegacy::get_rows
, which passes the request to the MHBlockLegacy
subobjects and thence to the MHRowLegacy
leaf objects. The MHRowLegacy::get_rows
method just returns the single Pobox::Schema::Result::Condition
objects that its target encapsulates. The MHCondLegacy::get_rows
method assembles several rows into a block, adjusting some of the fields along the way, such as the ends_block
field, and the top-level MHCondLegacy::get_rows
method assembles the groups of block rows into a complete set of database rows representing an entire condition.