DBIx::Class is the first ORM I've found which provides more benefits than headaches. It's not perfect—it has a learning curve—but its power and flexibility have simplified several of my projects.
One of my few ambivalences toward DBIC is that it commingles database operations (create, retrieve, update, and destroy) with model-specific operations ("level up this character", "sell the magic sword", "deactivate the smoke trap"). This is useful in that my objects have smart persistence and updating and relationship tracking provided by their DBICness, but it's difficult in that sometimes I want a stricter separation of database concerns from data model concerns.
Suppose I want to test that a document processing model can successfully filter out many hundreds of thousands of algorithmically generated permutations of unwanted data. Suppose, as is the case, that these documents are DBIC objects normally retrieved from a database. A test database might seem like just the thing for data-driven testing, but in this case it seems more work to generate a large transient database full of hundreds of thousands of documents to satisfy the constraints of DBIC.
In other words, I want a way to generate a new model object programmatically
without having to store it in and retrieve it from a database. (Alex Hartmaier and Matt S. Trout both reminded me of the
$rs->new_result() method which creates an object given the appropriate resultset but does not store it in the database.)
I do have tests which verify that the storage concerns behave as appropriate. They represent a small investment in mock data which sufficiently exercises all of the cases my code needs to handle. My other test concerns have little to do with the database itself. They care about what the model objects do with their data, not how they get that data.
Thank goodness DBIC is compatible with Moose.
I extracted all of the non-database model methods into a role. That role requires the accessors the database model provides for persistent data. I created a very simple dummy class which has those necessary attributes and performs that role. Then I wrote a very, very simple loader function which generates the necessary data algorithmically, instantiates instances of the dummy class, and tests the role's methods.
(I plan to write a longer article for Perl.com showing example code.)
The result is a model class which consists solely of the code generated from
with statement to apply a couple of roles. The tests are in
two parts: one tests the model-specific code. The other tests the
persistence-specific code, itself a combination of the generated code and
another role which collects the remaining persistence behavior.
Even though both roles have an obvious coupling in terms of providing necessary behavior to the model (and to each other), decoupling them in terms of storage provides much improved testability—and, I suspect, more opportunities for reuse and genericity throughout the system.
While explanations of the value of Perl roles often focus on reusability and the separation of similar concerns from otherwise unrelated classes, roles can also provide a separation between dissimilar behaviors and concerns. In this case, it doesn't matter to the role methods where the data comes from (a live database, a testing database, an algorithm, hard-coded in a test case, the command line, wherever). It only matters that that data is there.
This technique doesn't always work. This technique isn't always appropriate. This technique does not replace verification that your behavior roles interoperate with your persistence models appropriately. Even so, it has simplified a lot of my code and improved my tests greatly.