DBIx-Class-PseudoEnum
view release on metacpan or search on metacpan
lib/DBIx/Class/PseudoEnum.pm view on Meta::CPAN
$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!
=head1 DESCRIPTION
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
L<philosophical and practical problems|https://chateau-logic.com/content/why-we-should-not-use-enums-databases>
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 C<DBIx::Class>
schema, and have it enforced within the application, invisibly to the DB.
=head1 SUBROUTINES/METHODS
=head2 enumerate( C<$field>, C<[$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
C<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 B<do not> get an C<is_undef> method. Yet. See LIMITATIONS below.
=head2 enumerations_use_column_names()
Calling this function will require the schema to create methods with the column name included. E.g. instead of
C<is_value>, you get C<fieldname_is_value> methods. It only operates on the Result class where you call it.
=head1 DEPENDENCIES
=over 4
=item L<Carp>
=item L<DBIx::Class>
=item L<Modern::Perl>
=item L<Sub::Quote>
=back
=head1 BUGS AND LIMITATIONS
Bugs? What bugs? (No, really. If you find one, open an issue, please.)
The following limitations are (currently) present:
=over 4
=item B<Text columns only!>:
At present, you may only use this with text-based columns.
=item B<Collisions>:
If you have two enumerated fields in a table, and their lower-cased, underscore-punctuated
values collide, the code will choose the B<last> one that you defined with an C<enumerate>
statement. In this instance, you should probably use C<enumerations_use_column_names> to force
column names to be listed.
If you have multiple enumerated values in a single field that collide on their lower-cased,
underscore-punctuated values, then B<any> of them will respond to test methods: e.g. if you
have C<BLUE> and C<blue> values in an enumeration, then C<is_blue> will be true for either one.
(...but why would you do that?)
=item B<undef>
If a field is nullable in the DB and the schema, you do not get an C<is_undef> method. Yet.
=item B<Case-insensitive>
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).
=item B<Adding to existing code>
If you have an application where you add this module's functionality after there is data in
the table, it B<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.
=item B<Error handling>
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 L<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.
=back
=head1 ROADMAP
I have these features in mind, going forward.
=over 4
=item * Handle non-text columns
=item * Automatically detect and force collision behavior
( run in 2.106 seconds using v1.01-cache-2.11-cpan-0b5f733616e )