=head1 NAME Handel::Manual::Upgrading - A guide to upgrading your Handel installation. =head1 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. =head1 UPGRADING 0.3x TO 1.0 =head2 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. =head3 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. =head3 Search is the new load The C method in Cart/Order has been renamed to C. 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; =head3 Create is the new 'new' The C method in Cart/Order/Item has been renamed to C. 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; =head3 Order 'process' flag is now an option hash The 'process' argument to Order-Cnew 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}); =head3 Currency convert w/ format options is gone In 0.3x, the C method of currency objects would return a formatted string if you passed in the C 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 C 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 C afterwords. If C and C are the same, convert now returns itself instead of undef. =head3 Currency format is now as_string Handel::Currency is now a subclass of Data::Currency. As such, C is now a simple getter/setter for the format of a given currency object. The act of formatting an object has been moved to C. The C method now returns the same thing as C 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; =head2 Subclassing Changes The following changes will have to be made to any custom subclasses of the Handel classes: =head3 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 L, and each has its own instance of L. 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); I That's all there is to it. Any other Class::DBI method, like C, C, 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. C, C and C are now set in L itself. C, C and C 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; B you are using the default Handel schema. See L for the list of available options for things like add/remove columns, add/remove/constraints, setting the item class, autoupdates, etc. =head3 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 C better match the C 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); =head3 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 ... }; C<$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 C<$object> and C<$changing>. Any rows changed in C<$data> will be set just as if you had also called $result-Eset_column($column, $value). =head2 Compat Layer If you're really really daring and don't want to change code for now, You can load L 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 C or 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. =head1 SEE ALSO L, L, L, L, L =head1 AUTHOR Christopher H. Laco CPAN ID: CLACO claco@chrislaco.com http://today.icantfocus.com/blog/