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 )