The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Handel::Manual::Upgrading - A guide to upgrading your Handel installation.

DESCRIPTION

Handel 1.0 is a major update from the 0.3x versions. While the public API is mostly compatible, there are a few changes to what is returned from the public methods. How subclasses are configured has also completely changed in 1.0. Depending on how much customization you have done, you may or may not have to make a lot of changes to your existing code. While there are many many changes under the hood, this document will only cover changes that effect the public API, or changes that effect subclassing Handel classes.

UPGRADING 0.3x TO 1.0

Public API Changes

The following changes will have to be made to any software using the default classes in Handel 0.33 and below after upgrading to Handel 1.0.

RETURN_AS is dead.

The RETURN_AS constants and parameters are dead. Gone. Buried. They are an ex-parrot. They were a bad idea from my inexperience at dealing with context issues in Template Toolkit.

All of the methods that used them now only return one of two things: A list of objects in list context, or a Handel::Iterator in scalar context. No single/multiple result magic exists.

Version 0.3x

    my $cart = Handel::Cart->load({id => '11111111-1111-1111-1111-111111111111'});
    # $cart isa Handel::Cart
    
    print $cart->name;

Version 1.0

    my $it = Handel::Cart->search({id => '11111111-1111-1111-1111-111111111111'});
    # $it isa Handel::Iterator
    
    my $cart = $it->first;
    # $cart isa Handel::Cart
    
    print $cart->name;

This effects all calls to search/items in Handel::Cart and Handel::Order. If you are using the Template Toolkit plugins, they now always return a Handel::Iterator object. If you need/want an list of results, you can simply call $iterator->all to get them within TT.

Search is the new load

The load method in Cart/Order has been renamed to search.

Version 0.3x

    my $cart = Handel::Cart->load({id => '11111111-1111-1111-1111-111111111111'});
    # $cart isa Handel::Cart
    
    print $cart->name;

Version 1.0

    my $it = Handel::Cart->search({id => '11111111-1111-1111-1111-111111111111'});
    # $it isa Handel::Iterator
    
    my $cart = $it->first;
    # $cart isa Handel::Cart
    
    print $cart->name;

Create is the new 'new'

The new method in Cart/Order/Item has been renamed to create.

Version 0.3x

    my $cart = Handel::Cart->new({id => '11111111-1111-1111-1111-111111111111'});
    
    print $cart->name;

Version 1.0

    my $it = Handel::Cart->create({id => '11111111-1111-1111-1111-111111111111'});

    print $cart->name;

Order 'process' flag is now an option hash

The 'process' argument to Order-gtnew is now a hash reference of options. If you are using the process argument, simple change it from a true value to a hash reference containing the process option.

Version 0.3x

    my $order = Handel::Order->new({
        cart => '11111111-1111-1111-1111-111111111111'
    }, 1);

Version 1.0

    my $order = Handel::Order->create({
        cart => '11111111-1111-1111-1111-111111111111'
    }, {process => 1});

Currency convert w/ format options is gone

In 0.3x, the convert method of currency objects would return a formatted string if you passed in the format parameter, otherwise it would return a currency object containing the newly converted price value. Returning two different things form the same method is silly. The formatting part of convert has been removed.

Version 0.3x

    my $price = Handel::Currency->new(1.23);
    
    # $1.67 Canadian Dollars
    print $price->convert('USD', 'CAD', 1, 'FMT_COMMON');
    
    # Handel::Currency
    print ref $price->convert('USD', 'CAD');

Version 1.0

    my $price = Handel::Currency->new(1.23);
    
    # 1.67
    print $price->convert('USD', 'CAD');
    
    # $1.67 Canadian Dollars
    print $price->convert('USD', 'CAD')->as_string('FMT_COMMON');

When upgrading, change all of your calls to convert to also call format afterwords. If from and to are the same, convert now returns itself instead of undef.

Currency format is now as_string

Handel::Currency is now a subclass of Data::Currency. As such, format is now a simple getter/setter for the format of a given currency object. The act of formatting an object has been moved to as_string. The stringify method now returns the same thing as as_string instead of returning the raw numeric value.

Version 0.3x

    my $price = Handel::Currency->new(1.23);
    
    print $price->format('FMT_COMMON');

Version 1.0

    my $price = Handel::Currency->new(1.23);
    $price->format('FMT_COMMON');
    
    print $price->as_string;

Subclassing Changes

The following changes will have to be made to any custom subclasses of the Handel classes:

Classes are not Class::DBI classes

In version 0.3x, the Cart/Order/Item classes were subclasses of Handel::DBI, which was a subclass of Class::DBI itself. In 1.0, Cart/Order/Item classes are no longer Class::DBI subclasses. Instead, they each subclass Handel::Base, and each has its own instance of Handel::Storage. Handel::Storage is now responsible for all of the data/schema related settings. As such, most of the common data-related settings have either been moved into the local instance of storage, or have been pushed way down into the schema itself.

In general, one only has to move most of the settings down a level into the storage instance. Here's is a brief example of the changes necessary for add_constraint/add_column:

Version 0.3x

    package MyCart;
    use strict;
    use warnings;
    use base qw/Handel::Cart/;
    
    __PACKAGE__->add_columns(qw/foo bar/);
    __PACKAGE__->add_constraints('Check Name', 'name' => \&mysub);

Version 1.0

    package MyCart;
    use strict;
    use warnings;
    use base qw/Handel::Cart/;
    
    __PACKAGE__->storage->add_columns(qw/foo bar/);
    __PACKAGE__->storage->add_constraints('name', 'Check Name' => \&mysub);

Take special note of the parameter order for add_constraint. More on that below.

That's all there is to it. Any other Class::DBI method, like has_many, columns, etc now reside in the Handel::Cart/Order::Schema itself. For most subclasses, this will be the extent of the changes necessary to get everything to work. If you have performed deeper magic to add other data relations, you will have to create your own schema, rather than use the default schema. Don't worry, that's what Handel 1.0 is all about: interchanging schemas; possibly without even changing any code in your program.

As another example, here's the top section of Handel::Cart before and after the changes:

Version 0.3x

    use base qw/Handel::DBI/;
    __PACKAGE__->mk_classdata(_item_class => 'Handel::Cart::Item');
    __PACKAGE__->autoupdate(1);
    __PACKAGE__->table('cart');
    __PACKAGE__->iterator_class('Handel::Iterator');
    __PACKAGE__->columns(All => qw(id shopper type name description));
    __PACKAGE__->has_many(_items => 'Handel::Cart::Item', 'cart');
    __PACKAGE__->add_constraint('id',      id      => \&constraint_uuid);
    __PACKAGE__->add_constraint('shopper', shopper => \&constraint_uuid);
    __PACKAGE__->add_constraint('type',    type    => \&constraint_cart_type);

Version 1.0

    use base qw/Handel::Base/;
    __PACKAGE__->storage_class('Handel::Storage::Cart');
    __PACKAGE__->storage({
        schema_class   => 'Handel::Cart::Schema',
        schema_source  => 'Carts',
        item_class     => 'Handel::Cart::Item',
        constraints    => {
            id      => {'Check Id'      => \&constraint_uuid},
            shopper => {'Check Shopper' => \&constraint_uuid},
            type    => {'Check Type'    => \&constraint_cart_type},
            name    => {'Check Name'    => \&constraint_cart_name}},
        default_values => {
            id         => \&Handel::Storage::uuid,
            type       => CART_TYPE_TEMP}
    });

For the most part, their concepts are quite similar. table, columns and has_many are now set in Handel::Schema::Cart itself. autoupdate, item_class and add_constraint are now set on the local storage object instead of on the cart object itself.

Things like constraints and columns can be set either in the storage arguments, or you can set them afterwords using the method syntax:

    use base qw/Handel::Base/;
    __PACKAGE__->storage_class('Handel::Storage::Cart');
    __PACKAGE__->storage({
        schema_class   => 'Handel::Cart::Schema',
        schema_source  => 'Carts',
        item_class     => 'Handel::Cart::Item',
        default_values => {
            id         => \&Handel::Storage::uuid,
            type       => CART_TYPE_TEMP}
    });
    __PACKAGE__->storage->add_constraint('id',      'Check Id'      => \&constraint_uuid);
    __PACKAGE__->storage->add_constraint('shopper', 'Check Shopper' => \&constraint_uuid);
    __PACKAGE__->storage->add_constraint('type',    'Check Type'    => \&constraint_cart_type);
    __PACKAGE__->storage->add_constraint('name',    'Check Name'    => \&constraint_cart_name);

The two examples above are exactly the same in terms of functionality. In terms of migration, it's mostly just a matter of changing how some options get set; if you are using the default Handel schema.

See Handel::Storage for the list of available options for things like add/remove columns, add/remove/constraints, setting the item class, autoupdates, etc.

add_constraints parameters are reversed

As with Class::DBI, you can have more than on constraint tied to the same column. All constraints are stored internally by name, grouped by column. In an effort to make add_constraint better match the constraints storage setup option and the internal configuration data, the parameters have been reversed:

Version 0.3x

    __PACKAGE__->add_constraint('ConstraintName', 'columnname' => \&mysub);

Version 1.0

    __PACKAGE__->storage->add_constraint('columnname', 'ConstraintName' => \&mysub);

Constraint methods receive different parameters

The constraint_* methods in Handel::Constraints followed the Class::DBI constraint format. Now that we're not using Class::DBI any more, the format has remained similar, but contains slightly different things:

Version 0.3x

    sub constraint_checkit {
        my ($value, $object, $column, $changing) = @_;
        # $value:    the field value
        # $object:   the actual cart object
        # $column:   the column name
        # $changing: hashref of changing values
        ...
    };

Version 1.0

    sub constraint_checkit {
        my ($value, $result, $column, $data) = @_;
        # $value:  the field value
        # $result: the actual schema result/row object. NOT a cart object
        # $column: the column name
        # data:    hashref of column values
        ...
    };

$result is a DBIx::Class::ResultSource/Row object that can be used to get/set or inspect any column in the current row, taking the place of of both the old Class::DBI $object and $changing. Any rows changed in $data will be set just as if you had also called $result->set_column($column, $value).

Compat Layer

If you're really really daring and don't want to change code for now, You can load Handel::Compat in your subclasses:

    package MyCustomCart;
    use strict;
    use warnings;
    use base qw/Handel::Compat Handel::Cart/;

Handel::Compat will map most of the common subclass settings like add_constraint, add_columns, and table to the new storage layer format. It will also provide the pre 1.0 new/load/items syntax that uses the now dead RETURN_AS constants.

If you are using the convert or <format> methods from Handel::Currency objects, you will need to load Handel::Compat::Currency in your subclasses as well:

    package MyCustomCart;
    use strict;
    use warnings;
    use base qw/Handel::Compat Handel::Cart/;
    
    __PACKAGE__->storage->currency_class('Handel::Compat::Currency');

This tells the storage layer to use Handel::Compat::Currency instead of Handel::Currency when inflating currency columns. If you really need to abuse the system, you can also resort to:

    use Handel::Currency;
    use Handel::Compat::Currency;
    {
        no strict;
        
        *Handel::Currency::new = Handel::Compat::Currency->can('new');
        *Handel::Currency::convert = Handel::Compat::Currency->can('convert');
        *Handel::Currency::format = Handel::Compat::Currency->can('format');
    };

But don't do that. If you do, kittens will cry.

SEE ALSO

Handel::Base, Handel::Storage, DBIx::Class::Schema, Handel::Compat, Data::Currency

AUTHOR

    Christopher H. Laco
    CPAN ID: CLACO
    claco@chrislaco.com
    http://today.icantfocus.com/blog/