On 1/30/07, Derek Watson <[EMAIL PROTECTED]> wrote: > I am using RDBO in conjunction with Rose::HTML::Form for the first time, and > wonder if there are any guides or examples out there showing some > conventional integration techniques. Specifically, how best to use RHF's > objects returned by object_from_form() style methods to update database > rows. Currently I have something like this: > > $form->init_with_customer($session->customer); # is a > load()ed RDBO object > if ($ENV{REQUEST_METHOD} eq 'POST') { > > $form->params(\%ARGS); > $form->init_fields(); > > if ($form->validate) { > my $c = $form->customer_from_form; # is RDBO::Customer->new() > $c->id($session->customer_id); # avoid an insert > $c->save(update => 1, changes_only => 1, cascade => 1); # force an > update > } > } > > Which works, but seems like there is probably a more conventional way to do > this.
Well, the details depend on your particular app and on the web application framework that you're using (if any). What you have there looks sensible to me. One thing I would suggest is that you consider having the customer_from_form() method do all the stuff required to set up the object (e.g., setting the id). In the example above, it's a little tricky because the value for the id is in $session, which the form knows nothing about. But in most web app frameworks, there's usually some way to get at "globally applicable" data like the session, in which case it's reasonable for customer_from_form() to return you an object that's completely ready to be save()d, without any fiddling, and, ideally, without need for an explicit update => ... argument. To this end, consider having customer_from_form() internally load(speculative => 1) the RDBO-derived object based on the id first, and then pass that object to object_from_form(). This will let you have forms that contain only a subset of the object attributes, and it will also ensure that a save() call does the right thing (an update, in this case). > But my real problem comes when my form returns other objects as well, > which are related to customer ($c) . . .for example > > if ($form->validate) { > my $c = $form->customer_from_form; # is RDBO::Customer->new() > $c->id($session->customer_id); # avoid an insert > > my $a = $form->address_from_form; # is RDBO::Address->new() > $c->billing_address($a); > > # inserts a new RDBO::Address record, but unfortunately I want an > # update > $c->save(update => 1, changes_only => 1, cascade => 1); > } > > Is there a better way to use these two packages together? I have db_object_from_form() and init_with_db_object() methods that will handle a tree of RDBO-derived objects that correspond to a tree of nested RHTMLO forms. The code is included at the end of this email, but it's still "in progress" and may be buggy. Take from it what you will. The basic idea is that, if a Person has a related Address, then the PersonAddress form is a PersonForm with a nested AddressForm using the same name as the relationship between Person and Address. Example: package Person; ... __PACKAGE__->meta->setup ( ... relationships => [ address => { type => 'one to one', class => 'Address', column_map => { id => 'person_id' }, }, ] ); ... package PersonForm; use base 'Rose::HTML::Form'; ... sub build_form { my($self) = shift; $self->add_fields ( name => { ... }, age => { ... }, ... ); } ... package PersonAddressForm; use base 'PersonForm'; ... sub build_form { my($self) = shift; $self->SUPER::build_form(@_); # Adds Person fields # Add a sub-form for Address under the same name as the one-to-one # RDBO relationship between Person and Address: "address" $self->add_form(address => AddressForm->new); } sub person_from_form { my($self) = shift; my $person = Person->new; if(my $id = $self->field('id')->internal_value) { $person->id($id); $person->load(with => 'address'); } # Returns a Person object complete with an Address sub-object return $self->db_object_from_form($person); } sub init_with_person { my($self, $person) = @_; $self->init_with_db_object($person); } Then, in action: $form = PersonAddressForm->new; ...init form with params, validate, etc.... # The $person object returned has an address sub-object $person = $form->person_from_form; # Save the Person and its Address in a single transaction $person->save; The code for db_object_from_form() and init_with_db_object() is below. Remember, it may be buggy and/or unsuitable for your purposes. It's just something I've been playing with. If/when I'm happy with it, it'll find its way into Rose::HTML::Form, or perhaps Rose::HTML::Form::RDBO or something. -John --- package My::Form; use strict; use base 'Rose::HTML::Form'; use Rose::DB::Object::Util qw(has_loaded_related); use Rose::HTML::Form::Constants qw(FF_SEPARATOR); # Variables for use in regexes our $FF_SEPARATOR_RE = quotemeta FF_SEPARATOR; our $FF_SEPARATOR = FF_SEPARATOR; our $Debug = 0; sub db_object_from_form { my($self) = shift; my($class, $object); if(@_ == 1) { $class = shift; if(ref $class) { $object = $class; $class = ref $object; } } elsif(@_) { my %args = @_; $class = $args{'class'}; $object = $args{'object'}; } else { croak "Missing required object class argument"; } $object ||= $class->new(); my $parent_object = $object; unless($object->isa('Rose::DB::Object')) { croak "$object is not a Rose::DB::Object-derived object"; } my $meta = $object->meta; foreach my $field ($self->fields) { my $name = $field->name; next unless($self->param_exists_for_field($name)); $object = $parent_object; my $partial_name = ''; if($name =~ /$FF_SEPARATOR_RE/o) { my $nibble = $name; my $obj = $object; while($nibble =~ s/^([^$FF_SEPARATOR]+)(?:$FF_SEPARATOR_RE)//o) { my $related = $1; last unless($obj->can($related)); if(has_loaded_related($obj, $related)) { $obj = $obj->$related() } else { my $new_obj; eval { $new_obj = $obj->$related() }; if($@ || !$new_obj) { # Restore failed segment $nibble = "$related$FF_SEPARATOR$nibble"; last; } $obj = $new_obj; } } if($nibble =~ /$FF_SEPARATOR_RE/o) { $name = $field->local_name; } else { $name = $nibble; $object = $obj; } } else { $name = $field->local_name; } if($object->can($name)) { # Checkboxes setting boolean columns if($field->isa('Rose::HTML::Form::Field::Checkbox') && $meta->column($name) && $meta->column($name)->type eq 'boolean') { #$Debug && warn "$object->$name(", $field->is_on, ")\n"; $object->$name($field->is_on); } else # everything else { my $value = $field->internal_value; $value = undef unless(length $value); #$Debug && warn "$object->$name($value)\n"; eval { $object->$name($value) }; if($@) { $self->error("Could not set $object->$name($value) - $@"); warn $self->error; return undef; } } } } return $parent_object; } sub init_with_db_object { my($self, $object) = @_; croak "Missing required object argument" unless($object); $self->clear(); foreach my $field (sort { $a->name cmp $b->name } $self->fields) { my $name = $field->name; if($name =~ /$FF_SEPARATOR_RE/o) { my $nibble = $name; my $obj = $object; while($nibble =~ s/^([^$FF_SEPARATOR]+)$FF_SEPARATOR_RE//o) { my $related = $1; last unless($obj->can($related)); if(has_loaded_related($obj, $related)) { $obj = $obj->$related() } else { my $new_obj; eval { $new_obj = $obj->$related() }; if($@ || !$new_obj) { # Restore failed segment $nibble = "$related$FF_SEPARATOR$nibble"; last; } $obj = $new_obj; } } if($nibble =~ /$FF_SEPARATOR_RE/o) { $name = $field->local_name; } else { $name = $nibble; $object = $obj; } } else { $name = $field->local_name; } if($object->can($name)) { #$Debug && warn field($name) = $object->$name = ", $object->$name(), "\n"; $field->input_value(scalar $object->$name()); } } } 1; ------------------------------------------------------------------------- Take Surveys. Earn Cash. Influence the Future of IT Join SourceForge.net's Techsay panel and you'll get the chance to share your opinions on IT & business topics through brief surveys - and earn cash http://www.techsay.com/default.php?page=join.php&p=sourceforge&CID=DEVDEV _______________________________________________ Rose-db-object mailing list Rose-db-object@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/rose-db-object