DBIx::Class::PseudoEnum - Schema-based enumerations independent of database
version 1.0003
# In your Schema::Result class: __PACKAGE__->load_components('PseudoEnum'); # Load your enumerations into source_info directly __PACKAGE__->table('contraption'); __PACKAGE__->source_info( { enumerations => { 'status' => [qw/Sold Packaged Shipped/] } } # Or use the handy-dandy methods # (leave off the __PACKAGE__ if you're using DBIx::Class::Candy) __PACKAGE__->table('doodad'); __PACKAGE__->enumerate( 'status', [qw/Ordered In-Stock Out-Of-Stock/] ); __PACKAGE__->enumerate( 'color', [qw/Black Blue Green Red/] ); # Properly handle value collisions in the same table # (both fields here could create an 'is_blue' method!) __PACKAGE__->table('doohickey'); __PACKAGE__->enumerations_use_column_names(); __PACKAGE__->enumerate( 'field1', [qw/One Two Three Four Blue/] ); __PACKAGE__->enumerate( 'field2', [qw/BLUE RED GREEN/] ); # Later, in your application: # On Results: $doodad->is_ordered; # Boolean, true if doodad.status == 'Ordered' $doodad->is_blue; # Boolean, true if doodad.color == 'Blue' $doodad->update({ status => 'Dunno'}); # croaks! $doodad->update({ status => 'ordered' }); # croaks! $doodad->update({ color => 'Black' }); # okay! # The module will try to pass this on to the rest of the update() method; if the # field is nullable, it'll work. $doodad->update({ color => undef }); # On ResultSets: $doodad_rs->create({ status => 'Dunno' }); # croaks! $doodad_rs->create({ status => 'ordered' }); # croaks! $doodad_rs->create({ color => 'Black' }); # okay! $doodad_rs->is_blue # Returns a ResultSet where doodad.color == 'Blue' # With enumerations_use_column_names: $doohickey->is_blue # "no such method" $doohickey->field1_is_blue # Now it does what you want!
Enumerations can be a bit of a pain. Not all databases support them equally (or at all), which reduces the portability of your application. Additionally, there are some philosophical and practical problems with them. Lookup tables are an alternative, but maybe you don't want to clutter up your DB with single-column lookup tables.
But searching around the interwebs, no one seems to mind enumerating valid values for a data entity within the application layer. So that's what this module provides: a way to put the enumeration in the DBIx::Class schema, and have it enforced within the application, invisibly to the DB.
DBIx::Class
$field
[$value1, $value2,...]
This is the brains of the outfit, right here. The field must be a column in your table, and the values must be sent in as a hashref. Easy and obvious.
This method spins off methods in your Result and ResultSet classes for each value in your list, of the form is_value, which return a boolean (zero or one) if the current value of the enumerated field is the specified value. If the field is nullable, you do not get an is_undef method. Yet. See LIMITATIONS below.
is_value
is_undef
Calling this function will require the schema to create methods with the column name included. E.g. instead of is_value, you get fieldname_is_value methods. It only operates on the Result class where you call it.
fieldname_is_value
Bugs? What bugs? (No, really. If you find one, open an issue, please.)
The following limitations are (currently) present:
At present, you may only use this with text-based columns.
If you have two enumerated fields in a table, and their lower-cased, underscore-punctuated values collide, the code will choose the last one that you defined with an enumerate statement. In this instance, you should probably use enumerations_use_column_names to force column names to be listed.
enumerate
enumerations_use_column_names
If you have multiple enumerated values in a single field that collide on their lower-cased, underscore-punctuated values, then any of them will respond to test methods: e.g. if you have BLUE and blue values in an enumeration, then is_blue will be true for either one. (...but why would you do that?)
BLUE
blue
is_blue
If a field is nullable in the DB and the schema, you do not get an is_undef method. Yet.
To make the method name, this module replaces all non-alphanumeric characters with underscores, and smashes case on all upper-case letters. This may contribute to collisions (see above).
If you have an application where you add this module's functionality after there is data in the table, it will not complain about already-existing invalid values in enumerated fields. You will not, of course, be able to test for those values, nor set any other record to that value, unless you enumerate it.
If you've got a Row result, and try to update an enumerated field with an invalid value, it'll croak. That's probably what you want, but if you have that in, for instance, a Try::Tiny block, you then have a "dirty" column for your enumerated column, and the next update may mess with you by going ahead and *doing the update to the invalid value*. You can do $result->discard_changes, and not have to reload your object. This isn't a bug, precisely, but it is a known quirk, one that I'd like to eradicate.
I have these features in mind, going forward.
Handle non-text columns
Automatically detect and force collision behavior
Add an is_undef method for nullable fields
Option flag to make it work with case-sensitive enumerations
Method to hunt for 'invalid' values in the database and report
is_not_value methods
is_not_value
My boss at Clearbuilt really, really dislikes enumerations. Hopefully, this module will make them a bit easier for him to use.
Jason Crome encourages this sort of craziness fairly often.
D Ruth Holloway <ruth@hiruthie.me>
This software is copyright (c) 2023 by D Ruth Holloway.
This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.
To install DBIx::Class::PseudoEnum, copy and paste the appropriate command in to your terminal.
cpanm
cpanm DBIx::Class::PseudoEnum
CPAN shell
perl -MCPAN -e shell install DBIx::Class::PseudoEnum
For more information on module installation, please visit the detailed CPAN module installation guide.