# NAME DBIx::Class::PseudoEnum - Schema-based enumerations independent of database # VERSION version 1.0002 # SYNOPSIS # 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! # 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 [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 `DBIx::Class` schema, and have it enforced within the application, invisibly to the DB. # SUBROUTINES/METHODS ## enumerate( `$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. ## enumerations\_use\_column\_names() 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. # DEPENDENCIES - [Carp](https://metacpan.org/pod/Carp) - [DBIx::Class](https://metacpan.org/pod/DBIx%3A%3AClass) - [Modern::Perl](https://metacpan.org/pod/Modern%3A%3APerl) - [Sub::Quote](https://metacpan.org/pod/Sub%3A%3AQuote) # BUGS AND LIMITATIONS Bugs? What bugs? (No, really. If you find one, open an issue, please.) The following limitations are (currently) present: - **Text columns only!**: At present, you may only use this with text-based columns. - **Collisions**: 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. 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?) - **undef** If a field is nullable in the DB and the schema, you do not get an `is_undef` method. Yet. - **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). - **Adding to existing code** 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. # ROADMAP 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 # ACKNOWLEDGEMENTS My boss at Clearbuilt really, really dislikes enumerations. Hopefully, this module will make them a bit easier for him to use. [Jason Crome](https://metacpan.org/author/CROMEDOME) encourages this sort of craziness fairly often. # AUTHOR D Ruth Holloway # COPYRIGHT AND LICENSE This software is copyright (c) 2022 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.