| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package App::AutoCRUD::Controller::Table; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 1 |  |  | 1 |  | 647 | use 5.010; | 
|  | 1 |  |  |  |  | 4 |  | 
| 4 | 1 |  |  | 1 |  | 7 | use strict; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 27 |  | 
| 5 | 1 |  |  | 1 |  | 7 | use warnings; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 35 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 | 1 |  |  | 1 |  | 7 | use Moose; | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 10 |  | 
| 8 |  |  |  |  |  |  | extends 'App::AutoCRUD::Controller'; | 
| 9 | 1 |  |  | 1 |  | 11165 | use SQL::Abstract::More 1.27; | 
|  | 1 |  |  |  |  | 38 |  | 
|  | 1 |  |  |  |  | 13 |  | 
| 10 | 1 |  |  | 1 |  | 60 | use List::MoreUtils            qw/mesh firstval/; | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 16 |  | 
| 11 | 1 |  |  | 1 |  | 1012 | use JSON::MaybeXS (); | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 26 |  | 
| 12 | 1 |  |  | 1 |  | 7 | use URI; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 45 |  | 
| 13 |  |  |  |  |  |  |  | 
| 14 | 1 |  |  | 1 |  | 10 | use namespace::clean -except => 'meta'; | 
|  | 1 |  |  |  |  | 4 |  | 
|  | 1 |  |  |  |  | 14 |  | 
| 15 |  |  |  |  |  |  |  | 
| 16 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 17 |  |  |  |  |  |  | # entry point to the controller | 
| 18 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 19 |  |  |  |  |  |  | sub serve { | 
| 20 | 15 |  |  | 15 | 1 | 75 | my ($self) = @_; | 
| 21 |  |  |  |  |  |  |  | 
| 22 | 15 |  |  |  |  | 461 | my $context = $self->context; | 
| 23 |  |  |  |  |  |  |  | 
| 24 |  |  |  |  |  |  | # extract from path : table name and method to dispatch to | 
| 25 | 15 | 50 |  |  |  | 79 | my ($table, $meth_name) = $context->extract_path_segments(2) | 
| 26 |  |  |  |  |  |  | or die "URL too short, missing table and method name"; | 
| 27 | 15 | 50 |  |  |  | 112 | my $method = $self->can($meth_name) | 
| 28 |  |  |  |  |  |  | or die "no such method: $meth_name"; | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | # set default template and title | 
| 31 | 15 |  |  |  |  | 568 | $context->set_template("table/$meth_name.tt"); | 
| 32 | 15 |  |  |  |  | 416 | $context->set_title($context->title . "-" . $table); | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | # dispatch to method | 
| 35 | 15 |  |  |  |  | 69 | return $self->$method($table); | 
| 36 |  |  |  |  |  |  | } | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  |  | 
| 39 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 40 |  |  |  |  |  |  | # published methods | 
| 41 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | sub descr { | 
| 44 | 9 |  |  | 9 | 1 | 34 | my ($self, $table) = @_; | 
| 45 |  |  |  |  |  |  |  | 
| 46 | 9 |  |  |  |  | 72 | my $datasource = $self->datasource; | 
| 47 | 9 |  |  |  |  | 61 | my $descr      = $datasource->config(tables => $table => 'descr'); | 
| 48 |  |  |  |  |  |  |  | 
| 49 |  |  |  |  |  |  | # datastructure describing this table | 
| 50 | 9 |  |  |  |  | 349 | return {table       => $table, | 
| 51 |  |  |  |  |  |  | colgroups   => $datasource->colgroups($table), | 
| 52 |  |  |  |  |  |  | primary_key => [$datasource->primary_key($table)], | 
| 53 |  |  |  |  |  |  | descr       => $descr}; | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  |  | 
| 58 |  |  |  |  |  |  | sub list { | 
| 59 | 2 |  |  | 2 | 1 | 10 | my ($self, $table) = @_; | 
| 60 |  |  |  |  |  |  |  | 
| 61 | 2 |  |  |  |  | 60 | my $context    = $self->context; | 
| 62 | 2 |  |  |  |  | 61 | my $req_data   = $context->req_data; | 
| 63 | 2 |  |  |  |  | 57 | my $datasource = $context->datasource; | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | # the "message" arg is sent once from inserts/updates/deletes; not to | 
| 66 |  |  |  |  |  |  | # be repeated in links to other queries | 
| 67 | 2 |  |  |  |  | 11 | my $message = delete $req_data->{-message}; | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | # dashed args are set apart | 
| 70 | 2 |  |  |  |  | 9 | my %where_args  = %$req_data; # need a clone because of deletes below | 
| 71 | 2 |  |  |  |  | 57 | my %dashed_args = $context->view->default_dashed_args($context); | 
| 72 | 2 |  |  |  |  | 85 | foreach my $arg (grep {/^-/} keys %where_args) { | 
|  | 1 |  |  |  |  | 6 |  | 
| 73 | 0 |  |  |  |  | 0 | $dashed_args{$arg} = delete $where_args{$arg}; | 
| 74 |  |  |  |  |  |  | } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | # some dashed args are treated here (not sent to the SQL request) | 
| 77 | 2 |  |  |  |  | 8 | my $with_count = delete $dashed_args{-with_count}; | 
| 78 | 2 |  |  |  |  | 6 | my $template   = delete $dashed_args{-template}; | 
| 79 | 2 | 50 |  |  |  | 9 | $context->set_template($template) if $template; | 
| 80 |  |  |  |  |  |  |  | 
| 81 |  |  |  |  |  |  | # select from database | 
| 82 | 2 |  | 50 |  |  | 106 | my $criteria  = $datasource->query_parser->parse(\%where_args) || {}; | 
| 83 | 2 |  |  |  |  | 6953 | my $statement = $datasource->schema->db_table($table)->select( | 
| 84 |  |  |  |  |  |  | -where => $criteria, | 
| 85 |  |  |  |  |  |  | %dashed_args, | 
| 86 |  |  |  |  |  |  | -result_as => 'statement', | 
| 87 |  |  |  |  |  |  | ); | 
| 88 | 2 |  |  |  |  | 12708 | my $rows         = $statement->select(); | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | # recuperate SQL for logging / informational purposes | 
| 91 | 2 |  |  |  |  | 7315 | my ($sql, @bind) = $statement->sql; | 
| 92 | 2 |  |  |  |  | 53 | my $show_sql     = join " / ", $sql, @bind; | 
| 93 | 2 |  |  |  |  | 29 | $self->logger({level => 'debug', message => $show_sql}); | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | # assemble results | 
| 96 | 2 |  |  |  |  | 51 | my $data = $self->descr($table); | 
| 97 | 2 |  |  |  |  | 46 | $data->{rows}       = $rows; | 
| 98 | 2 |  |  |  |  | 6 | $data->{message}    = $message; | 
| 99 | 2 |  |  |  |  | 7 | $data->{criteria}   = $show_sql; | 
| 100 | 2 | 50 |  |  |  | 10 | if ($with_count) { | 
| 101 | 0 |  |  |  |  | 0 | $data->{row_count}  = $statement->row_count; | 
| 102 | 0 |  |  |  |  | 0 | $data->{page_count} = $statement->page_count; | 
| 103 |  |  |  |  |  |  | } | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # links to prev/next pages | 
| 106 |  |  |  |  |  |  | $self->_add_links_to_other_pages($data, $req_data, | 
| 107 |  |  |  |  |  |  | $dashed_args{-page_index}, | 
| 108 | 2 |  |  |  |  | 16 | $dashed_args{-page_size}); | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | # link to update/delete forms | 
| 111 |  |  |  |  |  |  | $data->{where_args} = $self->_query_string( | 
| 112 | 2 |  |  |  |  | 9 | map { ("where.$_" => $where_args{$_}) } keys %where_args, | 
|  | 1 |  |  |  |  | 7 |  | 
| 113 |  |  |  |  |  |  | ); | 
| 114 |  |  |  |  |  |  |  | 
| 115 | 2 |  |  |  |  | 90 | return $data; | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | sub _add_links_to_other_pages { | 
| 120 | 2 |  |  | 2 |  | 9 | my ($self, $data, $req_data, $page_index, $page_size) = @_; | 
| 121 |  |  |  |  |  |  |  | 
| 122 | 2 | 50 | 33 |  |  | 18 | return unless defined $page_index && defined $page_size; | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 2 |  |  |  |  | 9 | $data->{page_index}    = $page_index; | 
| 125 | 2 |  |  |  |  | 9 | $data->{offset}        = ($page_index - 1) * $page_size + 1; | 
| 126 | 2 |  |  |  |  | 15 | $data->{similar_query} = $self->_query_string(%$req_data, | 
| 127 |  |  |  |  |  |  | -page_index => 1); | 
| 128 |  |  |  |  |  |  | $data->{next_page}     = $self->_query_string(%$req_data, | 
| 129 |  |  |  |  |  |  | -page_index => $page_index+1) | 
| 130 | 2 | 50 |  |  |  | 9 | unless @{$data->{rows}} < $page_size; | 
|  | 2 |  |  |  |  | 21 |  | 
| 131 | 2 | 50 |  |  |  | 14 | $data->{prev_page}     = $self->_query_string(%$req_data, | 
| 132 |  |  |  |  |  |  | -page_index => $page_index-1) | 
| 133 |  |  |  |  |  |  | unless $page_index <= 1; | 
| 134 |  |  |  |  |  |  | } | 
| 135 |  |  |  |  |  |  |  | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  |  | 
| 138 |  |  |  |  |  |  | sub id { | 
| 139 | 5 |  |  | 5 | 0 | 20 | my ($self, $table) = @_; | 
| 140 |  |  |  |  |  |  |  | 
| 141 | 5 |  |  |  |  | 29 | my $data     = $self->descr($table); | 
| 142 |  |  |  |  |  |  |  | 
| 143 | 5 |  |  |  |  | 127 | my $pk       = $data->{primary_key}; | 
| 144 | 5 |  |  |  |  | 167 | my @vals     = $self->context->extract_path_segments(scalar(@$pk)); | 
| 145 | 5 |  |  |  |  | 59 | my %criteria = mesh @$pk, @vals; | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | # get row from database | 
| 148 | 5 |  |  |  |  | 29 | my $row = $self->datasource->schema->db_table($table)->fetch(@vals); | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | # assemble results | 
| 151 | 5 |  |  |  |  | 18979 | $data->{row}    = $row; | 
| 152 | 5 |  |  |  |  | 27 | $data->{pk_val} = join "/", @vals; | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | # links | 
| 155 | 5 |  |  |  |  | 26 | my %where_pk = map { ("where_pk.$_" => $criteria{$_}) } keys %criteria; | 
|  | 5 |  |  |  |  | 36 |  | 
| 156 | 5 |  |  |  |  | 32 | $data->{where_pk} = $self->_query_string(%where_pk); | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 5 |  |  |  |  | 34 | return $data; | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | sub search { | 
| 163 | 2 |  |  | 2 | 0 | 6 | my ($self, $table) = @_; | 
| 164 |  |  |  |  |  |  |  | 
| 165 | 2 |  |  |  |  | 64 | my $context  = $self->context; | 
| 166 | 2 |  |  |  |  | 57 | my $req_data = $context->req_data; | 
| 167 |  |  |  |  |  |  |  | 
| 168 | 2 | 100 |  |  |  | 54 | if ($context->req->method eq 'POST') { | 
| 169 | 1 |  | 50 |  |  | 26 | my $output = delete $req_data->{-output} || ""; | 
| 170 | 1 | 50 |  |  |  | 3 | my $cols   = [keys %{delete $req_data->{col} || {}}]; | 
|  | 1 |  |  |  |  | 8 |  | 
| 171 | 1 |  |  |  |  | 6 | $req_data->{-columns} = join ",", @$cols; | 
| 172 | 1 |  |  |  |  | 8 | $self->redirect("list$output?" . $self->_query_string(%$req_data)); | 
| 173 |  |  |  |  |  |  | } | 
| 174 |  |  |  |  |  |  | else { | 
| 175 |  |  |  |  |  |  | # display the search form | 
| 176 | 1 |  | 50 |  |  | 18 | my @cols = split /,/, (delete $req_data->{-columns} || ""); | 
| 177 | 1 |  |  |  |  | 5 | $req_data->{"col.$_"} = 1 foreach @cols; | 
| 178 | 1 |  |  |  |  | 6 | my $data = $self->descr($table); | 
| 179 | 1 |  |  |  |  | 27 | $data->{init_form} = $self->_encode_json($req_data); | 
| 180 | 1 |  |  |  |  | 6 | return $data; | 
| 181 |  |  |  |  |  |  | } | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | sub update { | 
| 186 | 3 |  |  | 3 | 0 | 10 | my ($self, $table) = @_; | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 3 |  |  |  |  | 18 | $self->_check_canmodify; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 3 | 50 |  |  |  | 85 | if ($self->context->req->method eq 'POST') { | 
| 191 | 3 |  |  |  |  | 35 | $self->_do_update_data($table); | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  | else { | 
| 194 | 0 |  |  |  |  | 0 | $self->_display_update_form($table); | 
| 195 |  |  |  |  |  |  | } | 
| 196 |  |  |  |  |  |  | } | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | sub _check_canmodify { | 
| 199 | 8 |  |  | 8 |  | 20 | my ($self) = @_; | 
| 200 |  |  |  |  |  |  |  | 
| 201 | 8 | 50 |  |  |  | 225 | if ($self->context->app->readonly) { | 
| 202 | 0 |  |  |  |  | 0 | die 'readonly mode'; | 
| 203 |  |  |  |  |  |  | } | 
| 204 |  |  |  |  |  |  | } | 
| 205 |  |  |  |  |  |  |  | 
| 206 |  |  |  |  |  |  | sub _do_update_data { | 
| 207 | 3 |  |  | 3 |  | 10 | my ($self, $table) = @_; | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 3 |  |  |  |  | 11 | $self->_check_canmodify; | 
| 210 |  |  |  |  |  |  |  | 
| 211 | 3 |  |  |  |  | 81 | my $context    = $self->context; | 
| 212 | 3 |  |  |  |  | 85 | my $req_data   = $context->req_data; | 
| 213 | 3 |  |  |  |  | 86 | my $datasource = $context->datasource; | 
| 214 |  |  |  |  |  |  |  | 
| 215 |  |  |  |  |  |  | # columns to update | 
| 216 | 3 |  | 50 |  |  | 13 | my $to_set = $req_data->{set} || {}; | 
| 217 | 3 |  |  |  |  | 11 | foreach my $key (keys %$to_set) { | 
| 218 | 3 |  |  |  |  | 9 | my $val = $to_set->{$key}; | 
| 219 | 3 | 50 |  |  |  | 14 | delete $to_set->{$key} if ! length $val; | 
| 220 | 3 | 50 |  |  |  | 14 | $to_set->{$key} = undef if $val eq 'Null'; | 
| 221 |  |  |  |  |  |  | } | 
| 222 | 3 | 50 |  |  |  | 45 | keys %$to_set or die "nothing to update"; | 
| 223 |  |  |  |  |  |  |  | 
| 224 |  |  |  |  |  |  | # build filtering criteria | 
| 225 | 3 | 100 |  |  |  | 27 | my $where  = $req_data->{where} or die "update without any '-where' clause"; | 
| 226 | 2 |  |  |  |  | 76 | my $criteria = $datasource->query_parser->parse($where); | 
| 227 | 2 | 100 | 66 |  |  | 1214 | $criteria and keys %$criteria or die "update without any '-where' criteria"; | 
| 228 |  |  |  |  |  |  |  | 
| 229 |  |  |  |  |  |  | # perform the update | 
| 230 | 1 |  |  |  |  | 34 | my $db_table  = $datasource->schema->db_table($table); | 
| 231 | 1 |  |  |  |  | 141 | my $n_updates = $db_table->update(-set => $to_set, -where => $criteria); | 
| 232 |  |  |  |  |  |  |  | 
| 233 |  |  |  |  |  |  | # redirect to a list to display the results | 
| 234 | 1 | 50 |  |  |  | 2887 | my $message = ($n_updates == 1) ? "1 record was updated" | 
| 235 |  |  |  |  |  |  | : "$n_updates records were updated"; | 
| 236 |  |  |  |  |  |  | # TODO: $message could repeat the $to_set pairs | 
| 237 | 1 |  |  |  |  | 8 | my $query_string = $self->_query_string(%$where, -message => $message); | 
| 238 | 1 |  |  |  |  | 9 | $self->redirect("list?$query_string"); | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | sub _display_update_form { | 
| 242 | 0 |  |  | 0 |  | 0 | my ($self, $table) = @_; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 0 |  |  |  |  | 0 | $self->_check_canmodify; | 
| 245 |  |  |  |  |  |  |  | 
| 246 | 0 |  |  |  |  | 0 | my $context    = $self->context; | 
| 247 | 0 |  |  |  |  | 0 | my $req_data   = $context->req_data; | 
| 248 | 0 |  |  |  |  | 0 | my $datasource = $context->datasource; | 
| 249 | 0 |  |  |  |  | 0 | my $data       = $self->descr($table); | 
| 250 |  |  |  |  |  |  |  | 
| 251 | 0 | 0 |  |  |  | 0 | if (my $where_pk  = delete $req_data->{where_pk}) { | 
| 252 |  |  |  |  |  |  | # we got the primary key of one single record | 
| 253 | 0 |  |  |  |  | 0 | $data->{where_pk}  = $where_pk; | 
| 254 | 0 |  |  |  |  | 0 | $req_data->{where} = $where_pk; | 
| 255 |  |  |  |  |  |  |  | 
| 256 |  |  |  |  |  |  | # fetch current values so that we can display them on page | 
| 257 | 0 |  |  |  |  | 0 | my $criteria = $datasource->query_parser->parse($where_pk); | 
| 258 | 0 |  |  |  |  | 0 | my $db_table = $datasource->schema->db_table($table); | 
| 259 | 0 |  |  |  |  | 0 | $req_data->{curr} = $db_table->select(-where     => $criteria, | 
| 260 |  |  |  |  |  |  | -result_as => 'firstrow'); | 
| 261 |  |  |  |  |  |  | } | 
| 262 |  |  |  |  |  |  | else { | 
| 263 |  |  |  |  |  |  | # we got criteria that may touch several records | 
| 264 | 0 |  |  |  |  | 0 | $self->_mark_multicols_keys($data); | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | # fields that should not be updatable | 
| 268 | 0 | 0 |  |  |  | 0 | if (my $noupd = delete $req_data->{_noupd}) { | 
| 269 | 0 |  |  |  |  | 0 | $data->{noupd}{$_} = 1 foreach split qr[/], $noupd; | 
| 270 |  |  |  |  |  |  | } | 
| 271 |  |  |  |  |  |  |  | 
| 272 |  |  |  |  |  |  | # initial values for the form | 
| 273 | 0 |  |  |  |  | 0 | $data->{init_form} = $self->_encode_json($req_data); | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 0 |  |  |  |  | 0 | return $data; | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | sub delete { | 
| 280 | 2 |  |  | 2 | 0 | 6 | my ($self, $table) = @_; | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 2 |  |  |  |  | 9 | $self->_check_canmodify; | 
| 283 |  |  |  |  |  |  |  | 
| 284 | 2 |  |  |  |  | 54 | my $context    = $self->context; | 
| 285 | 2 |  |  |  |  | 54 | my $req_data   = $context->req_data; | 
| 286 | 2 |  |  |  |  | 57 | my $datasource = $context->datasource; | 
| 287 |  |  |  |  |  |  |  | 
| 288 | 2 | 50 |  |  |  | 53 | if ($context->req->method eq 'POST') { # POST => delete in database | 
| 289 |  |  |  |  |  |  | # build filtering criteria | 
| 290 | 2 | 100 |  |  |  | 34 | my $where = $req_data->{where} or die "delete without any '-where' clause"; | 
| 291 | 1 |  |  |  |  | 31 | my $criteria = $datasource->query_parser->parse($where); | 
| 292 | 1 | 50 | 33 |  |  | 84 | $criteria and keys %$criteria or die "delete without any '-where' criteria"; | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | # perform the delete | 
| 295 | 0 |  |  |  |  | 0 | my $db_table  = $datasource->schema->db_table($table); | 
| 296 | 0 |  |  |  |  | 0 | my $n_deletes = $db_table->delete(-where => $criteria); | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | # redirect to a list to display the results | 
| 299 | 0 | 0 |  |  |  | 0 | my $message = ($n_deletes == 1) ? "1 record was deleted" | 
| 300 |  |  |  |  |  |  | : "$n_deletes records were deleted"; | 
| 301 | 0 |  |  |  |  | 0 | my $query_string = $self->_query_string(%$where, -message => $message); | 
| 302 | 0 |  |  |  |  | 0 | $self->redirect("list?$query_string"); | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  | else {                                  # GET => display the delete form | 
| 305 |  |  |  |  |  |  | # display the delete form | 
| 306 | 0 |  |  |  |  | 0 | my $data = $self->descr($table); | 
| 307 | 0 | 0 |  |  |  | 0 | if (my $where_pk  = delete $req_data->{where_pk}) { | 
| 308 |  |  |  |  |  |  | # we got the primary key of one single record | 
| 309 | 0 |  |  |  |  | 0 | $data->{where_pk}  = $where_pk; | 
| 310 | 0 |  |  |  |  | 0 | $req_data->{where} = $where_pk; | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  | else { | 
| 313 |  |  |  |  |  |  | # we got criteria that may touch several records | 
| 314 | 0 |  |  |  |  | 0 | $self->_mark_multicols_keys($data); | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | # initial values for the form | 
| 318 | 0 |  |  |  |  | 0 | $data->{init_form} = $self->_encode_json($req_data); | 
| 319 |  |  |  |  |  |  |  | 
| 320 | 0 |  |  |  |  | 0 | return $data; | 
| 321 |  |  |  |  |  |  | } | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  |  | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  |  | 
| 327 |  |  |  |  |  |  | sub clone { | 
| 328 | 0 |  |  | 0 | 0 | 0 | my ($self, $table) = @_; | 
| 329 |  |  |  |  |  |  |  | 
| 330 | 0 |  |  |  |  | 0 | $self->_check_canmodify; | 
| 331 |  |  |  |  |  |  |  | 
| 332 | 0 |  |  |  |  | 0 | my $context = $self->context; | 
| 333 | 0 | 0 |  |  |  | 0 | $context->req->method eq 'GET' | 
| 334 |  |  |  |  |  |  | or die "the /clone URL only accepts GET requests"; | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | # get primary key | 
| 337 | 0 |  |  |  |  | 0 | my $data    = $self->descr($table); | 
| 338 | 0 |  |  |  |  | 0 | my $pk      = $data->{primary_key}; | 
| 339 | 0 |  |  |  |  | 0 | my %is_pk   = map {$_ => 1} @$pk; | 
|  | 0 |  |  |  |  | 0 |  | 
| 340 | 0 |  |  |  |  | 0 | my @vals    = $context->extract_path_segments(scalar(@$pk)); | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | # get row from database | 
| 343 | 0 |  |  |  |  | 0 | my $row = $self->datasource->schema->db_table($table)->fetch(@vals); | 
| 344 |  |  |  |  |  |  |  | 
| 345 |  |  |  |  |  |  | # populate req_data before calling insert() | 
| 346 | 0 |  |  |  |  | 0 | my $req_data   = $context->req_data; | 
| 347 | 0 |  |  |  |  | 0 | foreach my $col (keys %$row) { | 
| 348 | 0 |  |  |  |  | 0 | my $val = $row->{$col}; | 
| 349 | 0 | 0 | 0 |  |  | 0 | $req_data->{$col} = $val if $val and !$is_pk{$col}; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | # cheat with path (simulating a call to insert()) | 
| 353 | 0 |  |  |  |  | 0 | my $path = $context->path; | 
| 354 | 0 |  |  |  |  | 0 | $path =~ s/clone$/insert/; | 
| 355 | 0 |  |  |  |  | 0 | $context->set_path($path); | 
| 356 | 0 |  |  |  |  | 0 | $context->set_template('table/insert.tt'); | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | # forward to insert() | 
| 359 | 0 |  |  |  |  | 0 | $self->insert($table); | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  |  | 
| 363 |  |  |  |  |  |  | sub insert { | 
| 364 | 0 |  |  | 0 | 0 | 0 | my ($self, $table) = @_; | 
| 365 |  |  |  |  |  |  |  | 
| 366 | 0 |  |  |  |  | 0 | $self->_check_canmodify; | 
| 367 |  |  |  |  |  |  |  | 
| 368 | 0 |  |  |  |  | 0 | my $context    = $self->context; | 
| 369 | 0 |  |  |  |  | 0 | my $req_data   = $context->req_data; | 
| 370 | 0 |  |  |  |  | 0 | my $datasource = $context->datasource; | 
| 371 |  |  |  |  |  |  |  | 
| 372 | 0 | 0 |  |  |  | 0 | if ($context->req->method eq 'POST') { | 
| 373 |  |  |  |  |  |  | # perform the insert | 
| 374 | 0 |  |  |  |  | 0 | my $db_table  = $datasource->schema->db_table($table); | 
| 375 | 0 |  |  |  |  | 0 | my @pk = $db_table->insert($req_data); | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | # redirect to a list to display the results | 
| 378 | 0 |  |  |  |  | 0 | my $message = "1 record was inserted"; | 
| 379 | 0 |  |  |  |  | 0 | my $query_string = $self->_query_string(-message => $message); | 
| 380 | 0 |  |  |  |  | 0 | $self->redirect(join("/", "id", @pk) . "?$query_string"); | 
| 381 |  |  |  |  |  |  | } | 
| 382 |  |  |  |  |  |  | else { | 
| 383 |  |  |  |  |  |  | # display the insert form | 
| 384 | 0 |  |  |  |  | 0 | my $data = $self->descr($table); | 
| 385 | 0 |  |  |  |  | 0 | $data->{init_form} = $self->_encode_json($req_data); | 
| 386 |  |  |  |  |  |  |  | 
| 387 | 0 |  |  |  |  | 0 | return $data; | 
| 388 |  |  |  |  |  |  | } | 
| 389 |  |  |  |  |  |  | } | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  |  | 
| 393 |  |  |  |  |  |  | sub count_where { # used in Ajax mode by update and delete forms | 
| 394 | 0 |  |  | 0 | 0 | 0 | my ($self, $table) = @_; | 
| 395 |  |  |  |  |  |  |  | 
| 396 | 0 |  |  |  |  | 0 | my $context    = $self->context; | 
| 397 | 0 |  |  |  |  | 0 | my $req_data   = $context->req_data; | 
| 398 | 0 |  |  |  |  | 0 | my $datasource = $context->datasource; | 
| 399 |  |  |  |  |  |  |  | 
| 400 | 0 |  |  |  |  | 0 | my $n_records = -1; | 
| 401 |  |  |  |  |  |  |  | 
| 402 | 0 | 0 |  |  |  | 0 | if (my $where = $req_data->{where}) { | 
| 403 | 0 |  |  |  |  | 0 | my $criteria = $datasource->query_parser->parse($where); | 
| 404 | 0 | 0 | 0 |  |  | 0 | if ($criteria and keys %$criteria) { | 
| 405 | 0 |  |  |  |  | 0 | my $db_table  = $datasource->schema->db_table($table); | 
| 406 | 0 |  |  |  |  | 0 | my $result = $db_table->select( | 
| 407 |  |  |  |  |  |  | -columns   => 'COUNT(*)', | 
| 408 |  |  |  |  |  |  | -where     => $criteria, | 
| 409 |  |  |  |  |  |  | -result_as => 'flat_arrayref', | 
| 410 |  |  |  |  |  |  | ); | 
| 411 | 0 |  |  |  |  | 0 | $n_records = $result->[0]; | 
| 412 |  |  |  |  |  |  | } | 
| 413 |  |  |  |  |  |  | } | 
| 414 |  |  |  |  |  |  |  | 
| 415 | 0 |  |  |  |  | 0 | return {n_records => $n_records}; | 
| 416 |  |  |  |  |  |  | } | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 422 |  |  |  |  |  |  | # auxiliary methods | 
| 423 |  |  |  |  |  |  | #---------------------------------------------------------------------- | 
| 424 |  |  |  |  |  |  |  | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | sub _query_string { | 
| 427 | 11 |  |  | 11 |  | 54 | my ($self, %params) = @_; | 
| 428 | 11 |  |  |  |  | 25 | my @fragments; | 
| 429 |  |  |  |  |  |  | KEY: | 
| 430 | 11 |  |  |  |  | 47 | foreach my $key (sort keys %params) { | 
| 431 | 12 |  |  |  |  | 34 | my $val = $params{$key}; | 
| 432 | 12 | 100 |  |  |  | 52 | length $val or next KEY; | 
| 433 |  |  |  |  |  |  |  | 
| 434 |  |  |  |  |  |  | # cheap URI escape (for chars '=', '&', ';' and '+') | 
| 435 | 11 |  |  |  |  | 83 | s/=/%3D/g, s/&/%26/g, s/;/%3B/g, s/\+/%2B/g for $key, $val; | 
| 436 |  |  |  |  |  |  |  | 
| 437 | 11 |  |  |  |  | 47 | push @fragments, "$key=$val"; | 
| 438 |  |  |  |  |  |  | } | 
| 439 |  |  |  |  |  |  |  | 
| 440 | 11 |  |  |  |  | 70 | return join "&", @fragments; | 
| 441 |  |  |  |  |  |  | } | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  |  | 
| 444 |  |  |  |  |  |  | sub _encode_json { | 
| 445 | 1 |  |  | 1 |  | 4 | my ($self, $data) = @_; | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | # utf8-encoding is done in the view, so here we turn it off | 
| 448 | 1 |  |  |  |  | 10 | my $json_maker = JSON::MaybeXS->new(allow_blessed   => 1, | 
| 449 |  |  |  |  |  |  | convert_blessed => 1, | 
| 450 |  |  |  |  |  |  | utf8            => 0); | 
| 451 | 1 |  |  |  |  | 43 | return $json_maker->encode($data); | 
| 452 |  |  |  |  |  |  | } | 
| 453 |  |  |  |  |  |  |  | 
| 454 |  |  |  |  |  |  |  | 
| 455 |  |  |  |  |  |  | sub _mark_multicols_keys { | 
| 456 | 0 |  |  | 0 |  |  | my ($self, $data) = @_; | 
| 457 |  |  |  |  |  |  |  | 
| 458 | 0 | 0 |  |  |  |  | if (my $sep = $self->datasource->schema->sql_abstract->multicols_sep) { | 
| 459 |  |  |  |  |  |  | # in case of multi-columns keys, the form needs to add special fields | 
| 460 |  |  |  |  |  |  | # and to ignore regular fields for those columns | 
| 461 | 0 |  | 0 |  |  |  | my $where = $self->context->req_data->{where} || {}; | 
| 462 | 0 |  |  |  |  |  | my @multi_cols_keys = grep m[$sep], keys %$where; | 
| 463 | 0 |  |  |  |  |  | $data->{multi_cols_keys} = \@multi_cols_keys; | 
| 464 | 0 |  |  |  |  |  | $data->{ignore_col}{$_} = 1 foreach map {split m[$sep]} @multi_cols_keys; | 
|  | 0 |  |  |  |  |  |  | 
| 465 |  |  |  |  |  |  | } | 
| 466 |  |  |  |  |  |  | } | 
| 467 |  |  |  |  |  |  |  | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | 1; | 
| 470 |  |  |  |  |  |  |  | 
| 471 |  |  |  |  |  |  | __END__ | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  | =head1 NAME | 
| 474 |  |  |  |  |  |  |  | 
| 475 |  |  |  |  |  |  | App::AutoCRUD::Controller::Table - Table controller | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 478 |  |  |  |  |  |  |  | 
| 479 |  |  |  |  |  |  | This controller provides methods for searching and describing | 
| 480 |  |  |  |  |  |  | a given table within some datasource. | 
| 481 |  |  |  |  |  |  |  | 
| 482 |  |  |  |  |  |  | =head1 METHODS | 
| 483 |  |  |  |  |  |  |  | 
| 484 |  |  |  |  |  |  | =head2 serve | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  | Entry point to the controller; from the URL, it extracts the table | 
| 487 |  |  |  |  |  |  | name and the name of the method to dispatch to (the URL is expected | 
| 488 |  |  |  |  |  |  | to be of shape C<< table/{table_name}/{$method_name}?{arguments} >>). | 
| 489 |  |  |  |  |  |  | It also sets the default template to C<< table/{method_name}.tt >>. | 
| 490 |  |  |  |  |  |  |  | 
| 491 |  |  |  |  |  |  | =head2 descr | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | Returns a hashref describing the table, with keys C<descr> | 
| 494 |  |  |  |  |  |  | (description information from the config), C<table> (table name), | 
| 495 |  |  |  |  |  |  | C<colgroups> (datastructure as returned from | 
| 496 |  |  |  |  |  |  | L<App::AutoCRUD::DataSource/colgroups>), and | 
| 497 |  |  |  |  |  |  | C<primary_key> (arrayref of primary key columns). | 
| 498 |  |  |  |  |  |  |  | 
| 499 |  |  |  |  |  |  | =head2 list | 
| 500 |  |  |  |  |  |  |  | 
| 501 |  |  |  |  |  |  | Returns a list of records from the table, corresponding to the query | 
| 502 |  |  |  |  |  |  | parameters specified in the URL. | 
| 503 |  |  |  |  |  |  | [TODO: EXPLAIN MORE -- in particular the "-template" arg ] | 
| 504 |  |  |  |  |  |  |  | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  |  | 
| 507 |  |  |  |  |  |  |  | 
| 508 |  |  |  |  |  |  |  |