On 9/18/05, Dave Howorth <[EMAIL PROTECTED]> wrote:

> One of the issues I still don't understand is backwards compatibility.
> In this case, we're talking about removing at least two external
> modules. I think it's a good idea to do so. But how does somebody who's
> written a plugin for CGI::Untaint use it in the new scenario, for
> example? Or could I use my hacked about AsForm with it?
> 

I'm glad you asked, Dave.  Now i can get some explanation out of the way.   
 It stays backwards compatible because no interface changes.  AsForm
is just making inputs with to_cgi or to_field.  I've just added some
extra features as you have.   You can use your AsForm if you want.

 Forieign inputs is the only complex part about mine.  So ill just
give a quick example.
Hmm, Let me get a beer and see if a good one comes to me :) 
Say :

Label->columns('id', 'img_url', 'name');              # Beer Bottle label 
Ingredient->columns('id', 'type_enum', 'name', 'description');   # Ingredient
BeerIngred->columns('beer_id' , 'ingred_id', 'amout');  # Beer Ingred amount
BeerIngred->has_a(beer => Beer);
BeerIngred->has_a(ingred_id => Ingredient);
Beer->might_have(label =>Label) ;
Beer->has_many(ingredients => BeerIngred);

Now, say you call 
      Beer->to_field('label');  
What would you expect/want  to happen?  
Exactly,  You would get a hash of inputs for the Label class.  :)
      $beer_obj->to_field('label') works as expected too -- values of
the label object are filled in.

Now what if you want to put inputs for ingredients on your beer/addnew
template, as well?  You are in your template , merrily calling
to_field  on the beer columns.  Then you are ready to add 4 sets of
inputs for the 4 ingredients in beer.
What would you like to do?  You do not have the Ingedient class handy
in the classmeta data.  You could have put it there in the model
action. But wouldn't it be nicer to say
[% ingred_inputs =  $r->model_class->to_field('ingredients');
     # cleverly duplicate and display 4 times appending something
unique to each input name so you will be able to process it.
%] 

It sure would. And it is.  Inputs for Label and Ingredient are what I
call foreign inputs.  When you call to_field with my AsForm  , It
checks the meta data to see if the field is an accessor to a related
class. If it is, It calls "to_field' foreach display_column  (or
columns if there is no display_column sub) in that class/obj and
returns the hash ref of inputs**.

**has_a's get a select box as usual and the beer_id is skipped because
we are making a beer and would go into  never ending  recursion if it
was not skipped.

Afte making the inputs and before returning them,  AsForm renames
foreign inputs so that 1) they can be processed automatically much
like the way they are created and 2) Names do not clash with names in
the main class.  It uses a recursive sub  _rename_foreign_input that
can be overridden in your Beer class or any other. Here is what is in
the hash of ingedient inputs after saying
[%  ingredients = beer->to_field('ingredients'); %]

{   
       ingredients__FOREIGN__amount     =>   # input box
       ingredients__FOREIGN__ingred_id   =>   # select box of ingredient rows
}  

Inputs for beer have no accessor prefix and delimiter. They are
whatever the column names are.  Regular inputs:
name => #input
rating => #input
...

Now before you freak out over these crazy names pretend you are  a
Beer and you are wanting to process this huge cluster f*ck of inputs.
You're params are:

params => {
    name                                              => 'Crappy American Beer',
    rating                                              => 0,
    label__FOREIGN__img_url              => '/img/CAB_Label.png',
     label__FOREIGN__name                 => 'CAB Label',
    ingredients__FOREIGN__amount    => '5 lbs',
    ingredients__FOREIGN__ingred_id => 1,   # Corn
}

You could easily hash those according the the accessor with split,
{ name => ..,
   rating => ...,
   label => { 
         img_url => ...,
         name => ...
   }
   ingredients =>  { # *
        amount  => ...,
        ingred_id => ...
   }
}
This is exactly what the overrideable public method "classify_inputs"
does in SuperModel.

* ingredients would in reality be trickier since you would have 4 sets
but it could be generalized and doable -- instead of a hash you would
have an array of hashes. Just pretend there is one.)

With that done, You could now create the beer,  then add the beer_id
to the label and ingredient hashes, get the related class from the
meta info for those and  create them.  Assuming FromCGI worked this is
what a  Beer::do_edit would be something like:
sub do_edit {
   # Classify the inputs as above
    ....
    ....

   # Make 3 new untaint object
    .... 
    ....

   # Create the objects and relate
   my $label_data = delete params->{label};
   my $ingred_data = delete params->{ingredients};
   my $beer = $self->create_from_cgi {$h1, $params};  
   $label_data->{beer_id} = $beer->id;
   $ingred_data->{beer_id} = $beer->id;
   my $label = $self->create_from_cgi {$h2, $label_data };
   my $ingred= $self->create_from_cgi {$h3, $ingred_data };

   # Check errors, and delete objects if errors
    my %errs = $beer->cgi_update_errors;
    if (%errs) {
          $beer->delete;
          $r->template('addnew');
          $r->template_args->{errors} = \%errs;
     }
     else { 
         # go to view
     }
      return $beer
}
     
Then you are done.  Whew. A lot of work.  Maypole is not fun anymore.
Plus, Since FromCGI has no public interface to just validate the data
you have to create them in the DB and if the last one has errors, then
you just delete them.  Or you could not use it which is even more
work. In reality, you would not use it here and have more work for 2
reasons: 1) It does not do what you need  and 2) It is BROKEN.

SuperModel uses the interface of FromCGI and stole a little code/ideas
 and FromCGI is no longer used at all.  It has an easy to use method
validate_all that will just check for and return errors and do no
create.

my $errs = $self->validate_all ($r, $params ); 
if ($errs ) {
}

It creates CGI::UntaintPatched handlers  then validates your data with
respect to your Maypole config->{table}{related_cols} .. {all_cols}
and { ignore_cols } if there are any.  It then returns the a hashref
of errors (with hashes for label and ingredients of course)   if there
are any.

(# Still with me?  Here comes the 1 backward incompatibility but it
can be remedied easily.)

Taking it a step further,   SuperModel's create_from_cgi sub does it
all for you .  So that instead of a all that classifying , creating
handlers , validating 3 times, and then creating  and relating the
objects, you would just say:

my $beer = $self->create_from_cgi($r, $r->params);  # [*]

[ I wait for the applause to die down ]
  
[*] Note the one current backward incompatible  first argument.
I think a good fix would be to allow first argument to be a $h or an $r.
This way people can use whatever CGI::Untaint *TYPE* object they want.
Very flexible.
Note also that classify_inputs is overrideable so people can do things
their own way.
Note also that create_from_cgi calls the public method validate_all .
In  FromCGI validate was not inherited. Now people can override it on
a class by class basis, For instance Dave B and Dave H can wrap their
validation scheme in it (Actually everyone can as anyone with a
working app has to have one since Maypole's is broken ;) ) .  Or if
you want to put some more sensible error messages in the error hash,
you could do it there in traditional Maypole style :
$r->config->{table}{untaint_err_msg} .. {required_err_msg}.

[ Hold for applause again ]

To bring it all together I have a generic template  
custom/display_inputs that displays the inputs and errors recursively,
saving more work.  Also I added a columns argument to AsForm to_cgi so
you can tell it what columns to make.

[Still with me?  Here is the grand finale. ]

To create and process the beer  like the example above,  In
traditional Maypole spirit and style  all you have to do is this:

# 1) Specify relationships as obove
# 2) Put label and ingredients in display_columns
Beer->display_columns( qw/ name rank label  ingredients/ );

[ Grin  ]

All current  relationships  I know about are supported including is_a. 
Also, It is still vapor ware but in  a day I could have it with better
has many support. So to get your 4 sets of inputs for ingredients you
could say :
Beer->display_columns( qw/ name rank label  ingredients ingredients
ingredients ingredients/ );

[Acknowledgements]
All this is only posible because Simon and Tony wrote some kick ass
modules to begin with.   Maypole and CDBI are awesome for their ease
of use.  AsForm and FromCGI as well but they were incomplete in my
eyes.   All i did was extend them and  all my code is based on their
existing code.

I'll get that demo up ASAP.

cheers,

Peter


-------------------------------------------------------
SF.Net email is sponsored by:
Tame your development challenges with Apache's Geronimo App Server. Download
it for free - -and be entered to win a 42" plasma tv or your very own
Sony(tm)PSP.  Click here to play: http://sourceforge.net/geronimo.php
_______________________________________________
Maypole-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/maypole-devel

Reply via email to