Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask

Understanding Catalyst

by QuillMeantTen (Friar)
on Oct 03, 2015 at 09:07 UTC ( #1143700=perlquestion: print w/replies, xml ) Need Help??
QuillMeantTen has asked for the wisdom of the Perl Monks concerning the following question:

Greetings fellow monks.
After having done some reconfiguring on a server I currently own (as in pay for, not hacked into) and used for a nodejs webapp I have been confronted with the following problem:

Since I badly patched together the nodejs app it would not run anymore. One evening of unsuccessful hacks later I decided that "screw it, I always hated javascript in the first place, now that I know perl I say good riddance I'll rewrite it using catalyst"

One week of on and off working on that project (university work had precedence) I now have a new, more maintainable and elegant web application.

I come here with two questions:

  • As usual, do you see any way to make the following code better (look at the bottom of the page), its my first time with catalyst so It must be quite smelly.
  • How does catalyst architecture works? more specifically please do correct (and|or) add to my current understanding

Regarding the second question here is my experience:

I have read The Definitive Guide to Catalyst but I dont think I correctly grasped all the concepts. The Model part is made of tt files (at least for web views) living in the root folder, those are the templates used to generate pages seen by the clients. Each of those tt files uses variables given to it by the controller using the command $context->stash.

The View part is the one I least understand. Here I only used View Web TT, meaning what is sent back to the client is web pages made from TT models

The Controller part is the engine that make it all run, it is the script where each url request is translated into an action to be taken.

What happens if I want to send back serialized objects instead of a webpage? What kind of model files would I need? What if I need to access a database, should the requests go in the controller?

As you can see I am still struggling and having made a first webapp did not give me more than a hint of the true power of this framework. thanks you for reading!

and now the code: my and

package duty_planner; use Moose; use namespace::autoclean; use Catalyst::Runtime 5.80; # Set flags and add plugins for the application. # # Note that ORDERING IS IMPORTANT here as plugins are initialized in o +rder, # therefore you almost certainly want to keep ConfigLoader at the head + of the # list if you're using it. # # -Debug: activates the debug mode for very useful log message +s # ConfigLoader: will load the configuration from a Config::General f +ile in the # application's home directory # Static::Simple: will serve static files from the application's root # directory use Catalyst qw/ -Debug ConfigLoader Static::Simple Session Session::Store::File Session::State::Cookie /; extends 'Catalyst'; our $VERSION = '0.01'; # Configure the application. # # Note that settings in duty_planner.conf (or other external # configuration file that you set up manually) take precedence # over this when using ConfigLoader. Thus configuration # details given here can function as a default configuration, # with an external configuration file acting as an override for # local deployment. __PACKAGE__->config( name => 'duty_planner', # Disable deprecated behavior needed by old applications disable_component_resolution_regex_fallback => 1, enable_catalyst_header => 1, # Send X-Catalyst header static=>{include_path=>['/tmp/duty_planner_files',duty_planner->co +nfig->{root}]}, uploadtmp => '/tmp/duty_planner_files', ); # Start the application __PACKAGE__->setup(); =encoding utf8 =head1 NAME duty_planner - Catalyst based application =head1 SYNOPSIS script/ =head1 DESCRIPTION [enter your description here] =head1 SEE ALSO L<duty_planner::Controller::Root>, L<Catalyst> =head1 AUTHOR root =head1 LICENSE This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut 1;
package duty_planner::Controller::Root; use Moose; use Data::GUID; use namespace::autoclean; use List::MoreUtils qw(onlyidx); use Archive::Zip qw( :ERROR_CODES :CONSTANTS ); BEGIN { extends 'Catalyst::Controller' } # # Sets the actions in this controller to be registered with no prefix # so they function identically to actions created in # __PACKAGE__->config(namespace => ''); =encoding utf-8 =head1 NAME duty_planner::Controller::Root - Root Controller for duty_planner =head1 DESCRIPTION [enter your description here] =head1 METHODS =head2 index The root page (/) =cut my $domain = ''; my @keys = qw(lenom nbtotal nblundi nbmardi nbmercredi nbjeudi nbvendr +edi nbsamedi nbdimanche); #dont forget to set filepath at end of file : create folder #/tmp/duty_planner_files my $jar_folder = "/home/urist/Documents/programing/perl/catalyst_t0rtug4/"; sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash(domain=>$domain); $c->session; } sub serviceform :Local :Args(0){ my ($self,$c) = @_; my ($service,$jours,$interieur,$delete); $service = $c->request->param('nom_service'); $jours = $c->request->param('nb_jours_repos'); $interieur = $c->request->param('interieur'); $delete = $c->request->param('delete'); if($service){ if(!$jours || $jours == 0){ $jours = 1; } if(!$interieur){ $interieur='false'; } else{ $interieur='true'; } $c->session->{service}->{$service}={nb_jours=>$jours,inter +ieur=>$interieur}; } if($delete){ my @todel = split(/,/,$delete); print "requesting deletion of @todel\n"; for my $i (0 .. $#todel){ delete($c->session->{service}->{$todel[$i]}); } } $c->stash(services=>$c->session->{service}); $c->stash(domain=>$domain); } sub medform :Local :Args(0){ my ($self,$c) = @_; my $nom = $c->request->param('nom_med'); my $last_shift = $c->request->param('dernieregarde'); my $service = $c->request->param('service'); my $delete = $c->request->param('delete'); if($delete){ my @todel = split(/,/,$delete); for my $i (0 .. $#todel){ delete($c->session->{medecins}->{$todel[$i]}); } } if($nom){ $c->session->{medecins}->{$nom}={service=>$service,dernier +egarde=>$last_shift}; } $c->stash(medecins=>$c->session->{medecins}); $c->stash(services=>$c->session->{service}); $c->stash(domain=>$domain); } sub ferieform :Local :Args(0){ my ($self,$c) = @_; my($delete,$nom,$date,$interieur); $nom = $c->request->param('nom_med'); $date = $c->request->param('date_ferie'); $interieur=$c->request->param('interieur'); $delete=$c->request->param('delete'); print "delete = $delete\n"; if(!$interieur){ $interieur = 'false'; } else{ $interieur='true'; } if($nom){ if(!defined($c->session->{feries}->{$date}->{$interieur}) +){#no one is there my $service = $c->session->{medecins}->{$nom}->{servic +e}; my $intable = $c->session->{service}->{$service}->{int +erieur}; print("intable for $nom = $intable\n"); if($intable =~/true/ || $interieur =~ /false/ ){ #no contradiction between requested and capability #be it as in duty our out duty $c->session->{feries}->{$date}->{$interieur} = $no +m; } } } if($delete){ my @todel = split(/,/,$delete); for my $i (0 .. $#todel){ my @couple = split(/_/,$todel[$i]); my $deldate = $couple[0]; my $delint = $couple[1]; delete($c->session->{feries}->{$deldate}->{$delint}); #if I deleted the last entry then delete date if(($delint =~ /false/ && !defined($c->session->{feries}->{$deldate}->{true} +)) ||($delint =~ /true/ && !defined($c->session->{fer +ies}->{$deldate}->{false}))){ delete($c->session->{feries}->{$deldate}); } } } $c->stash(medecins=>$c->session->{medecins}); $c->stash(services=>$c->session->{service}); $c->stash(ferie=>$c->session->{feries}); $c->stash(domain=>$domain); } sub infoform :Local :Args(0){ my ($self,$c) = @_; my $ddb = $c->request->param('datedebut'); my $ddf = $c->request->param('datefin'); my $delete = $c->request->param('delete'); if($delete){ delete($c->session->{infos}); } if($ddb && $ddf && !defined($c->session->{infos})){ $c->session->{infos} = [$ddb,$ddf]; } $c->stash(infos=>$c->session->{infos}); $c->stash(domain=>$domain); } sub optionform :Local :Args(0){ my ($self,$c) = @_; my $delete = $c->request->param('delete'); print "defined delete = ".defined($delete)."\n"; if($c->request->param('lenom')){ for my $i (1 .. $#keys){ print("adding ".$c->request->param($keys[$i])." for $keys[ +$i] for name = ".$c->request->param($keys[0])."\n"); $c->session->{options}->{$c->request->param($keys[0])}->[$ +i-1] = $c->request->param($keys[$i]); } } elsif(defined($delete)){ my @todel = split(/,/,$delete); foreach my $nom (@todel){ delete $c->session->{options}->{$nom}; } } $c->stash(options=>$c->session->{options}); $c->stash(medecins=>$c->session->{medecins}); $c->stash(domain=>$domain); } sub holidaysform :Local :Args(0){ my ($self,$c)=@_; my $nom = $c->request->param('nom'); my $ddb = $c->request->param('datedebut'); my $ddf= $c->request->param('datefin'); my $delete = $c->request->param('delete'); if($nom){ $c->session->{vacances}->{$nom}->{$ddb} = $ddf; print "added vacances from $ddb to ".$c->session->{vacances}->{$nom}->{$ddb}." for $nom\n"; } if($delete){ my @todel = split(/,/,$delete); for my $i (0 .. $#todel){ my @couple = split(/_/,$todel[$i]); my $delnom = $couple[0]; my $deldate = $couple[1]; delete($c->session->{vacances}->{$delnom}->{$deldate}); } } $c->stash(domain=>$domain); $c->stash(vacances=>$c->session->{vacances}); $c->stash(medecins=>$c->session->{medecins}); } sub make_planning :Local :Args(0){ my($self,$c) = @_; chdir "/tmp/duty_planner_files"; my $filepath = $c->sessionid; if(! -e $filepath.'_data.xls'){ print "no $filepath data xls\n"; open my $csv,'>',$filepath or die "cant open dest file: $!"; print $csv "<medecins>\n"; my $medecins = $c->session->{medecins}; for my $med (keys %$medecins){ print $csv "$med\n$medecins->{$med}->{service}\n$medecins->{$med +}->{dernieregarde}\n"; } print $csv "</medecins>\n<feries>\n"; my $feries = $c->session->{feries}; foreach my $date (keys %$feries){ foreach my $int (keys %{$feries->{date}}){ print $csv "$date\n$feries->{$date}->{$int}\n$int\n"; } } print $csv "</feries>\n<vacances>\n"; my $vacances = $c->session->{vacances}; foreach my $nom (keys %$vacances){ foreach my $ddb (keys %{$vacances->{$nom}}){ print $csv "$ddb\n$vacances->{$nom}->{$ddb}\n$nom\n"; } } print $csv "</vacances>\n<info>\n"; print $csv $c->session->{infos}->[0]."\n".$c->session->{infos} +->[1]."\n"; print $csv "</info>\n<services>\n"; my $services = $c->session->{service}; foreach my $servname (keys %$services){ print $csv "$servname\n$services->{$servname}->{interieur}\n$services +->{$servname}->{nb_jours}\n"; } print $csv "</services>\n<options>\n"; my $options = $c->session->{options}; foreach my $opt (keys %$options){ print $csv "$opt\n"; for my $i (0 .. $#keys-1){ print "nom = $opt\n cur key = $keys[$i]\nvalue = ".$options->{$opt}->[$i]."\n and i = $i\n"; my $toprint = $options->{$opt}->[$i]; $toprint = $i< $#keys?$toprint."\n":$toprint; print $csv $toprint; } } print $csv "</options>"; close $csv; print "executing java -jar $jar_folder $filepath\n"; my $cmd = "java -jar $jar_folder"."duty_planner.jar $filepath" +; `$cmd`; } else{ my $wd = `pwd`; my $cmd = "java -jar $jar_folder"."duty_planner.jar --xls ".$f +ilepath."_data.xls $filepath"; print "executing $cmd from $wd\n"; `$cmd`; } my $zip = Archive::Zip->new(); my $file_member = $zip->addFile($filepath."_data.xls","data.xls"); $file_member = $zip->addFile($c->sessionid."_planning_garde.xls","planning.xls"); unless ( $zip->writeToFileNamed($c->sessionid.'') +== AZ_OK ) { die 'write error'; } my @files = ($c->sessionid, $c->sessionid."_data.xls", $c->sessionid."_planning_garde.xls"); unlink @files; my $filename = $c->sessionid.''; open my $fh,'<',$filename; $c->response->header( 'Content-Disposition' => "attachment;" ); $c->response->body($fh); } sub fupload :Local :Args(0){ my ($self,$c)=@_; my $filename = $c->sessionid."_data.xls"; $c->stash(domain=>$domain); if ( $c->request->parameters->{form_submit} eq 'yes' ) { for my $field ( $c->req->upload ) { my $upload = $c->req->upload($field); my $target="/tmp/duty_planner_files/$filename"; unless ($upload->link_to($target)||$upload->copy_to($targe +t)){ die("Failedtocopy'$filename'to'$target':$!"); } } } } =head2 default Standard 404 error page =cut sub default :Path { my ( $self, $c ) = @_; $c->response->body( 'Page not found' ); $c->response->status(404); } =head2 end Attempt to render a view, if needed. =cut sub end : ActionClass('RenderView') {} =head1 AUTHOR root =head1 LICENSE This library is free software. You can redistribute it and/or modify it under the same terms as Perl itself. =cut __PACKAGE__->meta->make_immutable; 1;

Replies are listed 'Best First'.
Re: Understanding Catalyst
by Anonymous Monk on Oct 03, 2015 at 15:19 UTC

    you may find that interview about Catalyst helpful

    Pay attention to the question "What is the MVC pattern and how does Catalyst implement it?" ,answered by Matt Trout himself

Re: Understanding Catalyst
by sundialsvc4 (Abbot) on Oct 03, 2015 at 12:45 UTC

    Methinks that you have “Model” and “View” confused in your mind . . .

    (See Catalyst::Manual::Intro.)   The MVC view-of-the-world basically goes like this:

    • Model:   Access to data, manipulating data, allowing the rest of the application to functionally-ignore where the data comes from and how it is stored.
    • View:   The presentation layer.   Templating and output-preparation goes here.   (Not in the Model.)
    • Controller:   Everything else.

    And, while the MVC paradigm is a useful way to look at some things, it really only works really-well with CRUD (Create, Read, Update, Delete) applications, where the user’s conceptual model of what he is doing is fairly close to “I am manipulating records in a database, and I can do anything I want with anything that I see.”   When the application becomes more complex than that, the MVC model starts to be a bit simple-minded and application logic starts showing-up in places other than where MVC says it “ought” to be.   (Even given that the role, scope and bounds of “Controller” is already quite vague.)   Therefore, understand how the MVC abstraction is put together, but consider it to be an abstraction of what any given application may in fact require.

    Catalyst is a good framework.   Dancer is another.

      In this application I did not use a database, I used cookies and the $c->session hash to manage everything, would it be more appropriate to put all the manipulations inside the Model instead of the controller?

        I’m not sure that the MVC paradigm really addresses that concept very well.   If the application has no persistent storage, I’m not sure if one would say that it has a Model.

        However ... “there are no absolutes.”   MVC, like everything else, is a guideline not a mantra.   If you have developed something that works cleanly for you, I would not redesign it for the sake of philosophy.   :-)   Others may weigh-in with differing opinions.

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://1143700]
Approved by Corion
and all is quiet...

How do I use this? | Other CB clients
Other Users?
Others about the Monastery: (2)
As of 2018-01-21 10:50 GMT
Find Nodes?
    Voting Booth?
    How did you see in the new year?

    Results (227 votes). Check out past polls.