File Coverage

blib/lib/RapidApp/Module/DatStor.pm
Criterion Covered Total %
statement 160 311 51.4
branch 56 166 33.7
condition 18 82 21.9
subroutine 24 42 57.1
pod 0 34 0.0
total 258 635 40.6


line stmt bran cond sub pod time code
1             package RapidApp::Module::DatStor;
2 5     5   32 use Moose;
  5         9  
  5         27  
3             extends 'RapidApp::Module::ExtComponent';
4              
5 5     5   26585 use strict;
  5         11  
  5         104  
6 5     5   21 use RapidApp::Util qw(:all);
  5         10  
  5         1843  
7 5     5   157 use String::Random;
  5         12  
  5         221  
8 5     5   2120 use RapidApp::Module::DatStor::Column;
  5         12  
  5         193  
9 5     5   804 use MIME::Base64;
  5         1161  
  5         284  
10              
11 5     5   2090 use Text::Glob qw( match_glob );
  5         3396  
  5         19879  
12              
13             has 'create_handler' => ( is => 'ro', default => undef, isa => 'Maybe[RapidApp::Handler]' );
14             has 'read_handler' => ( is => 'ro', default => undef, isa => 'Maybe[RapidApp::Handler]' );
15             has 'update_handler' => ( is => 'ro', default => undef, isa => 'Maybe[RapidApp::Handler]' );
16             has 'destroy_handler' => ( is => 'ro', default => undef, isa => 'Maybe[RapidApp::Handler]' );
17              
18             # global variable/flag (set/localized in RapidApp::Module::StorCmp)
19             our $BATCH_UPDATE_IN_PROGRESS = 0; #<-- should use obj hash key instead of pkg
20              
21             has 'record_pk' => ( is => 'ro', default => undef );
22             has 'store_fields' => ( is => 'ro', default => undef );
23              
24             # ---
25             # Changed for GitHub Issue #100
26             #
27             # Ensure that the storeId is unique per request, but still able to be used to resolve
28             # the store for when 'defer_DataStore' is active
29             has 'storeId', is => 'ro', default => sub {
30             join('-','ds',String::Random->new->randregex('[a-z0-9A-Z]{6}'))
31             };
32             around storeId => sub {
33             my ($orig,$self,@args) = @_;
34             my $id = $self->$orig(@args);
35             my $c = $self->c;
36             $c ? join('-',$id,$c->request_id) : $id;
37             };
38             #
39             # ---
40              
41             has 'store_use_xtype' => ( is => 'ro', default => 0 );
42             has 'store_autoLoad' => ( is => 'rw', default => sub {\0} );
43             has 'reload_on_save' => ( is => 'ro', default => 1 );
44              
45             has 'max_pagesize' => ( is => 'ro', isa => 'Maybe[Int]', default => undef );
46              
47              
48             has 'onrequest_columns_mungers' => (
49             traits => [ 'Array' ],
50             is => 'ro',
51             isa => 'ArrayRef[RapidApp::Handler]',
52             default => sub { [] },
53             handles => {
54             all_onrequest_columns_mungers => 'uniq',
55             add_onrequest_columns_mungers => 'push',
56             insert_onrequest_columns_mungers => 'unshift',
57             has_no_onrequest_columns_mungers => 'is_empty',
58             }
59             );
60              
61             has 'read_raw_mungers' => (
62             traits => [ 'Array' ],
63             is => 'ro',
64             isa => 'ArrayRef[RapidApp::Handler]',
65             default => sub { [] },
66             handles => {
67             all_read_raw_mungers => 'uniq',
68             add_read_raw_mungers => 'push',
69             insert_read_raw_mungers => 'unshift',
70             has_no_read_raw_mungers => 'is_empty',
71             }
72             );
73              
74              
75             has 'update_mungers' => (
76             traits => [ 'Array' ],
77             is => 'ro',
78             isa => 'ArrayRef[RapidApp::Handler]',
79             default => sub { [] },
80             handles => {
81             all_update_mungers => 'uniq',
82             add_update_mungers => 'push',
83             insert_update_mungers => 'unshift',
84             has_no_update_mungers => 'is_empty',
85             }
86             );
87              
88              
89              
90             has 'base_params_mungers' => (
91             traits => [ 'Array', 'RapidApp::Role::PerRequestBuildDefReset' ],
92             is => 'ro',
93             isa => 'ArrayRef[RapidApp::Handler]',
94             default => sub { [] },
95             handles => {
96             all_base_params_mungers => 'uniq',
97             add_base_params_mungers => 'push',
98             has_no_base_params_mungers => 'is_empty',
99             }
100             );
101              
102             has 'base_keys' => (
103             traits => [ 'Array' ],
104             is => 'ro',
105             isa => 'ArrayRef',
106             default => sub { [] },
107             handles => {
108             add_base_keys => 'push',
109             base_keys_list => 'uniq'
110             }
111             );
112              
113             sub BUILD {
114 103     103 0 15057 my $self = shift;
115            
116 103         3507 $self->apply_actions( read => 'read' );
117 103 100       2816 $self->apply_actions( update => 'update' ) if (defined $self->update_handler);
118 103 100       2685 $self->apply_actions( create => 'create' ) if (defined $self->create_handler);
119 103 100       2610 $self->apply_actions( destroy => 'destroy' ) if (defined $self->destroy_handler);
120            
121 103 100       2656 $self->add_listener( write => RapidApp::JSONFunc->new( raw => 1, func =>
122             'function(store, action, result, res, rs) { store.reload(); }'
123             )) if ($self->reload_on_save);
124            
125            
126 103         2615 $self->add_base_keys($self->record_pk);
127            
128             # If this isn't in late we get a deep recursion error:
129 103         558 $self->add_ONREQUEST_calls('store_init_onrequest');
130             };
131              
132              
133             sub store_init_onrequest {
134 33     33 0 78 my $self = shift;
135            
136 33 100       1200 unless ($self->has_no_onrequest_columns_mungers) {
137 21         719 foreach my $Handler ($self->all_onrequest_columns_mungers) {
138 21         515 $Handler->call($self->columns);
139             }
140             }
141            
142             $self->apply_extconfig( baseParams => $self->base_params ) if (
143             defined $self->base_params and
144 33 50 33     793 scalar keys %{ $self->base_params } > 0
  33         654  
145             );
146            
147             ## Update update: turned back off due to possible caching issue (TODO: revisit)
148             ## -- Update: set the baseParams via merge just in case some earlier code has already set
149             ## some baseParams (not likely, but safer)
150             #if (defined $self->base_params and scalar keys %{$self->base_params} > 0) {
151             # my $baseParams = try{$self->get_extconfig_param('baseParams')} || {};
152             # %$baseParams = ( %$baseParams, %{$self->base_params} );
153             # $self->apply_extconfig( baseParams => $baseParams );
154             #}
155             ## --
156              
157 33         162 $self->apply_extconfig(
158             storeId => $self->storeId,
159             api => $self->store_api,
160             #baseParams => $self->base_params,
161             writer => $self->store_writer,
162             #autoLoad => $self->store_autoLoad,
163             autoSave => \0,
164             loadMask => \1,
165             autoDestroy => \1,
166             root => 'rows',
167             idProperty => $self->record_pk,
168             messageProperty => 'msg',
169             successProperty => 'success',
170             totalProperty => 'results',
171             #columns => $self->column_list
172             );
173            
174             # Set this to an object so that it can be modified in javascript
175             # *after* the store has been constructed:
176 33 50       923 $self->apply_extconfig( store_autoLoad => { params => {
    100          
177             start => 0,
178             limit => $self->max_pagesize ? $self->max_pagesize : 400
179             }}) if (jstrue $self->store_autoLoad);
180            
181             # If there is no Catalyst request, we can't get the base params:
182 33 100       114 if (defined $self->c) {
183 22         65 my $params = $self->get_store_base_params;
184             # Update Update: don't update via merge due to caching problem (TODO - investigate)
185             # -- Update: set the baseParams via merge just in case some earlier code has already set
186             # some baseParams (not likely, but safer)
187             #if (defined $params) {
188             # my $baseParams = $self->get_extconfig_param('baseParams') || {};
189             # %$baseParams = ( %$baseParams, %$params );
190             # $self->apply_extconfig( baseParams => $baseParams )
191             #}
192 22 50       101 $self->apply_extconfig( baseParams => $params ) if (defined $params);
193             # --
194             }
195            
196             }
197              
198              
199             sub JsonStore {
200 33     33 0 68 my $self = shift;
201            
202             return {
203 33 50       856 %{ $self->content },
  0         0  
204             xtype => 'jsonstore'
205             } if ($self->store_use_xtype);
206            
207 33         185 return RapidApp::JSONFunc->new(
208             func => 'new Ext.data.JsonStore',
209             parm => $self->content
210             );
211             }
212              
213              
214             sub get_store_base_params {
215 22     22 0 40 my $self = shift;
216 22         52 my $r_parms = $self->c->req->params;
217 22         1546 my $params = {};
218            
219             confess "base_params and base_params_base64 cannot be specified together" if (
220             exists $r_parms->{base_params} and
221             exists $r_parms->{base_params_base64}
222 22 0 33     74 );
223            
224             my $encoded = exists $r_parms->{base_params_base64} ?
225             decode_base64($r_parms->{base_params_base64}) :
226 22 50       89 $self->c->req->params->{base_params};
227            
228 22 50       1327 if (defined $encoded) {
229 0 0       0 my $decoded = $self->json->decode($encoded) or die "Failed to decode base_params JSON";
230 0         0 foreach my $k (keys %$decoded) {
231 0         0 $params->{$k} = $decoded->{$k};
232             }
233             }
234            
235 22         48 my $keys = [];
236 22         33 my $orig_params = {};
237 22         59 my $orig_params_enc = $self->c->req->params->{orig_params};
238 22 50       1305 $orig_params = $self->json->decode($orig_params_enc) if (defined $orig_params_enc);
239            
240 22         669 foreach my $key ($self->base_keys_list) {
241 22 50       68 $params->{$key} = $orig_params->{$key} if (defined $orig_params->{$key});
242 22 50       60 $params->{$key} = $self->c->req->params->{$key} if (defined $self->c->req->params->{$key});
243             }
244            
245 22 50       2046 unless ($self->has_no_base_params_mungers) {
246 0         0 foreach my $Handler ($self->all_base_params_mungers) {
247 0         0 $Handler->call($params);
248             }
249             }
250            
251 22 50       116 return undef unless (scalar keys %$params > 0);
252            
253 0         0 return $params;
254             }
255              
256             # Multisort
257             has 'multisort_enabled', is => 'ro', isa => 'Bool', default => 0;
258             has 'sorters', is => 'ro', isa => 'ArrayRef', default => sub {[]};
259              
260             # -- Moved from AppGrid2:
261             has 'columns' => ( is => 'rw', default => sub {{}}, isa => 'HashRef', traits => ['RapidApp::Role::PerRequestBuildDefReset'] );
262             has 'column_order' => ( is => 'rw', default => sub {[]}, isa => 'ArrayRef', traits => ['RapidApp::Role::PerRequestBuildDefReset'] );
263              
264             has 'include_columns' => ( is => 'ro', default => sub {[]} );
265             has 'exclude_columns' => ( is => 'ro', default => sub {[]} );
266              
267             has 'include_columns_hash' => ( is => 'ro', lazy => 1, default => sub {
268             my $self = shift;
269             my $hash = {};
270             foreach my $col (@{$self->include_columns}) {
271             $hash->{$col} = 1;
272             }
273             return $hash;
274             });
275              
276             has 'exclude_columns_hash' => ( is => 'ro', lazy => 1, default => sub {
277             my $self = shift;
278             my $hash = {};
279             foreach my $col (@{$self->exclude_columns}) {
280             $hash->{$col} = 1;
281             }
282             return $hash;
283             });
284              
285             sub has_column {
286 0     0 0 0 my $self = shift;
287 0         0 my $col = shift;
288 0 0       0 return 0 if ($self->deleted_column_names->{$col});
289 0 0       0 return 1 if (exists $self->columns->{$col});
290 0         0 return 0;
291             }
292              
293             sub get_column {
294 0     0 0 0 my $self = shift;
295 0         0 my $col = shift;
296 0 0       0 return undef unless ($self->has_column($col));
297 0         0 return $self->columns->{$col};
298             }
299              
300              
301             has 'deleted_column_names', is => 'ro', isa => 'HashRef', default => sub {{}}, traits => ['RapidApp::Role::PerRequestBuildDefReset'];
302             sub delete_columns {
303 0     0 0 0 my $self = shift;
304 0         0 my @columns = @_;
305 0         0 my %indx = map {$_=>1} @columns;
  0         0  
306            
307             # Besides deleting the column, add it to deleted_column_names to prevent
308             # it from being added back with apply_columns. This is just to prevent
309             # columns previously deleted from coming back which is probably not
310             # expected for desired. (although this could be considered breaking the API)
311             # This also means that columns can be deleted proactively (before they are added)
312             # -- This may be redundant to exclude_columns, need to look into combining these --
313 0         0 $self->deleted_column_names->{$_} = 1 for (@columns);
314            
315             # vvv -- deleted columns are now filtered out in column_list instead of using the below code -- vvv
316            
317             ## Delete by filtering out supplied column names:
318             #%{$self->columns} = map { $_ => $self->columns->{$_} } grep { !$indx{$_} } keys %{$self->columns};
319             #@{$self->column_order} = uniq(grep { !$indx{$_} } @{$self->column_order});
320             #
321             ##TODO: what happens if removed column had a read_raw_mungers/update_mungers?
322             #
323             #return $self->apply_columns;
324             }
325              
326              
327             sub get_columns_wildcards {
328 0     0 0 0 my $self = shift;
329 0         0 my @globspecs = @_;
330 0         0 my %cols = ();
331            
332 0         0 foreach my $gl (@globspecs) {
333 0   0     0 match_glob($gl,$_) and $cols{$_} = 1 for ($self->column_name_list);
334             }
335            
336 0         0 return keys %cols;
337             }
338              
339              
340             # Does the same thing as apply_columns, but the order is also set
341             # (offset should be the first arg). Unlike apply_columns, column data
342             # must be passed as a normal Hash (not Hashref). This is required
343             # because the order cannot be known
344             sub apply_columns_ordered {
345 0     0 0 0 my $self = shift;
346 0         0 my $offset = shift;
347            
348 0 0 0     0 die "invalid options passed to apply_columns_ordered" if (
349             ref($offset) or
350             ref($_[0])
351             );
352            
353 0         0 my %columns = @_;
354            
355             # Filter out previously deleted column names:
356             #%columns = map {$_=>$columns{$_}} grep { !$self->deleted_column_names->{$_} } keys %columns;
357            
358             # Get even indexed items from array (i.e. hash keys)
359 0         0 my @col_names = @_[map { $_ * 2 } 0 .. int($#_ / 2)];
  0         0  
360            
361 0         0 $self->apply_columns(%columns);
362 0         0 return $self->set_columns_order($offset,@col_names);
363             }
364              
365             sub apply_columns {
366 361     361 0 4224 my $self = shift;
367 361 100       1441 my %columns = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  92         596  
368            
369             # Filter out previously deleted column names:
370             #%columns = map {$_=>$columns{$_}} grep { !$self->deleted_column_names->{$_} } keys %columns;
371            
372 361         1188 foreach my $name (keys %columns) {
373            
374 1070 50       2589 next unless ($self->valid_colname($name));
375            
376 1070 100       23590 unless (defined $self->columns->{$name}) {
377 898         3153 $self->columns->{$name} = RapidApp::Module::DatStor::Column->new( name => $name );
378 898         1473 push @{ $self->column_order }, $name;
  898         20442  
379             }
380            
381 1070         23110 $self->columns->{$name}->apply_attributes(%{$columns{$name}});
  1070         6186  
382            
383 1070 100       25761 $self->add_read_raw_mungers($self->columns->{$name}->read_raw_munger) if ($self->columns->{$name}->read_raw_munger);
384 1070 50       23258 $self->add_update_mungers($self->columns->{$name}->update_munger) if ($self->columns->{$name}->update_munger);
385             }
386            
387 361         1204 return $self->apply_config(columns => $self->column_list);
388             }
389              
390             sub column_name_list {
391 494     494 0 693 my $self = shift;
392 494         757 return grep { !$self->deleted_column_names->{$_} } @{$self->column_order};
  5238         117595  
  494         11421  
393             }
394              
395             sub column_list {
396 491     491 0 807 my $self = shift;
397            
398             # new, safer way to way to handle deleted columns
399 491         1062 my @colnames = $self->column_name_list;
400            
401 491         950 my @list = ();
402 491         857 foreach my $name (@colnames) {
403 5199         120100 push @list, $self->columns->{$name}->get_grid_config;
404             }
405            
406 491         3674 return \@list;
407             }
408              
409              
410             sub apply_to_all_columns {
411 0     0 0 0 my $self = shift;
412 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
413            
414 0         0 foreach my $column (keys %{ $self->columns } ) {
  0         0  
415 0         0 $self->columns->{$column}->apply_attributes(%opt);
416             }
417            
418 0         0 return $self->apply_config(columns => $self->column_list);
419             }
420              
421             sub applyIf_to_all_columns {
422 0     0 0 0 my $self = shift;
423 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
424            
425 0         0 foreach my $column (keys %{ $self->columns } ) {
  0         0  
426 0         0 $self->columns->{$column}->applyIf_attributes(%opt);
427             }
428            
429 0         0 return $self->apply_config(columns => $self->column_list);
430             }
431              
432             sub apply_columns_list {
433 0     0 0 0 my $self = shift;
434 0         0 my $cols = shift;
435 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
436            
437 0 0       0 die "type of arg 1 must be ArrayRef" unless (ref($cols) eq 'ARRAY');
438            
439 0         0 foreach my $column (@$cols) {
440 0 0       0 croak "Can't apply_attributes because column '$column' is not defined\n" unless (defined $self->columns->{$column});
441 0         0 $self->columns->{$column}->apply_attributes(%opt);
442             }
443            
444 0         0 return $self->apply_config(columns => $self->column_list);
445             }
446              
447             # Pass a coderef and opts hash to apply columns. Coderef is called for each existing,
448             # non-deleted column. Column name is supplied to the coderef as $_ (and the first arg)
449             # For columns where the coderef returns true, the opts are applied.
450             sub apply_coderef_columns {
451 0     0 0 0 my $self = shift;
452 0         0 my $coderef = shift;
453            
454 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
455            
456             $coderef->($_) and $self->columns->{$_}->apply_attributes(%opt)
457 0   0     0 for ($self->column_name_list);
458            
459 0         0 return $self->apply_config(columns => $self->column_list);
460             }
461              
462             sub set_sort {
463 0     0 0 0 my $self = shift;
464 0 0       0 return $self->apply_config( sort_spec => {} ) unless (defined $_[0]);
465 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
466 0         0 return $self->apply_config( sort_spec => { %opt } );
467             }
468              
469             # batch_apply_opts_existing():
470             # Same as batch_apply_opts except columns that do not already exist
471             # are pruned out of columns and column_order
472             sub batch_apply_opts_existing {
473 0     0 0 0 my $self = shift;
474 0 0       0 my %opts = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
475            
476 0         0 foreach my $opt (keys %opts) {
477 0 0 0     0 if ($opt eq 'columns' and ref($opts{$opt}) eq 'HASH') {
    0          
478 0         0 foreach my $col (keys %{$opts{$opt}}) {
  0         0  
479 0 0       0 delete $opts{$opt}->{$col} unless (defined $self->columns->{$col});
480             }
481             }
482             elsif ($opt eq 'column_order') {
483 0         0 my @new_list = ();
484 0         0 foreach my $col (@{$opts{$opt}}) {
  0         0  
485 0 0       0 next unless (defined $self->columns->{$col});
486 0         0 push @new_list, $col;
487             }
488 0         0 @{$opts{$opt}} = @new_list;
  0         0  
489             }
490             }
491            
492 0         0 return $self->batch_apply_opts(\%opts);
493             }
494              
495             sub batch_apply_opts {
496 0     0 0 0 my $self = shift;
497 0 0       0 my %opts = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
498            
499 0         0 foreach my $opt (keys %opts) {
500 0 0 0     0 if ($opt eq 'columns' and ref($opts{$opt}) eq 'HASH') { $self->apply_columns($opts{$opt}); }
  0 0       0  
    0          
501 0         0 elsif ($opt eq 'column_order') { $self->set_columns_order(0,$opts{$opt}); }
502 0         0 elsif ($opt eq 'sort') { $self->set_sort($opts{$opt}); }
503             else {
504 0         0 $self->apply_extconfig( $opt => $opts{$opt} );
505             }
506             #elsif ($opt eq 'filterdata') { $self->apply_config($opt => $opts{$opt}); }
507             #elsif ($opt eq 'pageSize') { $self->apply_config($opt => $opts{$opt}); }
508             #else { die "invalid option '$opt' passed to batch_apply_opts"; }
509             }
510             }
511              
512              
513             sub valid_colname {
514 1070     1070 0 1553 my $self = shift;
515 1070         1800 my $name = shift;
516            
517 1070 50       1415 if (scalar @{$self->exclude_columns} > 0) {
  1070         25564  
518 0 0       0 return 0 if (defined $self->exclude_columns_hash->{$name});
519             }
520            
521 1070 50       1526 if (scalar @{$self->include_columns} > 0) {
  1070         24463  
522 0 0       0 return 0 unless (defined $self->include_columns_hash->{$name});
523             }
524            
525 1070         2479 return 1;
526             }
527              
528             sub set_columns_order {
529 97     97 0 1276 my $self = shift;
530 97         194 my $offset = shift;
531 97 50 33     624 my @cols = (ref($_[0]) eq 'ARRAY' and not defined $_[1]) ? @{ $_[0] } : @_; # <-- arg as list or arrayref
  0         0  
532            
533 97         265 my %cols_hash = ();
534 97         282 foreach my $col (@cols) {
535 806 50       1662 die $col . " specified more than once" if ($cols_hash{$col}++);
536             }
537            
538 97         214 my @pruned = ();
539 97         202 foreach my $col (@{ $self->column_order }) {
  97         2539  
540 806 50       1145 if ($cols_hash{$col}) {
541 806         1080 delete $cols_hash{$col};
542             }
543             else {
544 0         0 push @pruned, $col;
545             }
546             }
547            
548 97         289 my @remaining = keys %cols_hash;
549 97 50       356 if(@remaining > 0) {
550 0         0 die "can't set the order of columns that do not already exist (" . join(',',@remaining) . ')';
551             }
552            
553 97         203 my $last_indx = (scalar @pruned);
554 97 50       264 $offset = $last_indx if ($offset > $last_indx);
555            
556 97         439 splice(@pruned,$offset,0,@cols);
557            
558 97         194 @{ $self->column_order } = @pruned;
  97         2314  
559            
560 97         362 return $self->apply_config(columns => $self->column_list);
561             }
562              
563             # --
564              
565              
566              
567             #############
568              
569             sub params_from_request {
570 5     5 0 13 my $self= shift;
571            
572 5         23 my $params= $self->c->req->params;
573 5 50       412 if (defined $params->{orig_params}) {
574 0         0 $params= $self->json->decode($params->{orig_params});
575             }
576            
577 5         24 return $params;
578             }
579              
580             sub read {
581             # params is optional
582 8     8 0 32 my ($self, $params)= @_;
583            
584 8         53 $self->parent_module->enforce_permission;
585            
586             # only touch request if params were not supplied
587 8   66     3343 $params ||= $self->params_from_request;
588 8         40 $self->enforce_max_pagesize($params);
589            
590 8         39 my $data = $self->read_raw($params);
591            
592 8         37 return $self->meta_json_packet($data);
593             }
594              
595             # This is a safety measure to always apply a 'limit' to all requests to
596             # prevent a huge number of rows from being inadvertantly being fetched.
597             # It works by modifying the request params directly, which may not be
598             # the best solution, however it is being done here (and not in DbicLink2,
599             # for example) to apply this protection to *all* DataStores, not just
600             # DBIC-driven ones
601             sub enforce_max_pagesize {
602 8     8 0 25 my ($self, $params)= @_;
603            
604 8 50 33     214 return if !$self->max_pagesize || $params->{ignore_page_size};
605             return unless
606             not defined $params->{limit} or
607             $params->{limit} > $self->max_pagesize or
608 8 50 66     157 not defined $params->{start};
      66        
609            
610 3         8 my $new_params = {};
611 3 50       15 $new_params->{start} = 0 unless (defined $params->{start});
612             $new_params->{limit} = $self->max_pagesize if (
613             not defined $params->{limit} or
614 3 50 33     83 $params->{limit} > $self->max_pagesize
615             );
616            
617 3         25 %$params = (
618             %$params,
619             %$new_params
620             );
621             }
622              
623             sub read_raw {
624             # params is optional
625 8     8 0 27 my ($self, $params)= @_;
626            
627 8         17 my $data;
628 8 50 33     209 if (defined $self->read_handler and $self->has_flag('can_read')) {
629 8   33     43 $params ||= $self->params_from_request;
630            
631 8         190 $data = $self->read_handler->call($params);
632            
633             # data should be a hash with rows (arrayref) and results (number):
634             die "unexpected data returned in read_raw" unless (
635             ref($data) eq 'HASH' and
636             exists $data->{results} and
637 8 50 33     775 ref($data->{rows}) eq 'ARRAY'
      33        
638             );
639             } else {
640             # empty set of data:
641 0         0 $data= { results => 0, rows => [] }
642             }
643            
644 8 50       319 unless ($self->has_no_read_raw_mungers) {
645 8         261 foreach my $Handler ($self->all_read_raw_mungers) {
646 8         42 $Handler->call($data);
647             }
648             }
649            
650 8         25 return $data;
651             }
652              
653              
654             sub meta_json_packet {
655 8     8 0 15 my $self = shift;
656 8 50       26 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  8         39  
657            
658             # this "metaData" packet allows the store to be "reconfigured" on
659             # any request. Uuseful for things such as changing the fields, which
660             # we compute dynamically here from the first row of the data that was
661             # returned (see store_fields_from_rows)
662             return {
663             metaData => {
664             root => 'rows',
665             idProperty => $self->record_pk,
666             messageProperty => 'msg',
667             successProperty => 'success',
668             totalProperty => 'results',
669             fields => defined $self->store_fields ? $self->store_fields : $self->store_fields_from_rows($opt{rows}),
670             loaded_columns => $self->store_loaded_columns($opt{rows})
671 8 50       214 },
672             success => \1,
673             %opt
674             };
675             }
676              
677             sub store_loaded_columns {
678 8     8 0 20 my $self = shift;
679 8   50     22 my $rows = shift || [];
680 8 100       24 return [ keys %{$rows->[0]} ] if (scalar @$rows > 0);
  7         131  
681            
682             #If there are no rows, we assume *all* fields are loaded. Convert from 'store_fields'
683 1   33     24 my $fields = $self->store_fields || $self->store_fields_from_rows($rows);
684 1         3 return [ map { $_->{name} } @$fields ];
  5         32  
685             }
686              
687              
688             sub store_fields_from_rows {
689 0     0 0 0 my $self = shift;
690 0         0 my $rows = shift;
691            
692             # for performance we'll assume that the first row contains all the field types:
693 0         0 my $row = $rows->[0];
694            
695 0         0 my $fields = [];
696 0         0 foreach my $k (keys %$row) {
697 0         0 push @$fields, { name => $k };
698             }
699 0         0 return $fields;
700             }
701              
702              
703             sub update {
704 3     3 0 10 my $self = shift;
705            
706 3         23 $self->parent_module->enforce_permission;
707            
708 3         1488 my $params = $self->c->req->params;
709 3         342 my $rows = $self->json->decode($params->{rows});
710 3         1335 delete $params->{rows};
711            
712             # -- Set the $BATCH_UPDATE_IN_PROGRESS flag if the client says this is a
713             # batch update. This codepath is followed for "local" batch updates. See
714             # also the 'batch_update' method/action in RapidApp::Module::StorCmp
715 3         10 local $BATCH_UPDATE_IN_PROGRESS = $BATCH_UPDATE_IN_PROGRESS;
716 3 50       13 $BATCH_UPDATE_IN_PROGRESS = 1 if ($params->{batch_update});
717             # --
718            
719 3 50       14 if (defined $params->{orig_params}) {
720 0         0 my $orig_params = $self->json->decode($params->{orig_params});
721 0         0 delete $params->{orig_params};
722            
723             # merge orig_params, preserving real params that are set:
724 0         0 foreach my $k (keys %$orig_params) {
725 0 0       0 next if (defined $params->{$k});
726 0         0 $params->{$k} = $orig_params->{$k};
727             }
728             }
729            
730 3 50       121 unless ($self->has_no_update_mungers) {
731 0         0 foreach my $Handler ($self->all_update_mungers) {
732 0         0 $Handler->call($rows);
733             }
734             }
735            
736             #my $result = $self->update_records_coderef->($rows,$params);
737 3         81 my $result = $self->update_handler->call($rows,$params);
738             return $result if (
739             ref($result) eq 'HASH' and
740             defined $result->{success}
741 3 50 33     154 );
742            
743             return {
744 0 0         success => \1,
745             msg => 'Update Succeeded'
746             } if ($result);
747            
748 0           die "Update Failed";
749             }
750              
751              
752              
753              
754             sub create {
755 0     0 0   my $self = shift;
756            
757 0           $self->parent_module->enforce_permission;
758            
759 0           my $params = $self->c->req->params;
760 0           my $rows = $self->json->decode($params->{rows});
761 0           delete $params->{rows};
762            
763 0           my $result = $self->create_handler->call($rows);
764            
765             #scream_color(RED,$result);
766             # TODO: get rid of this crap into DbicLink2
767 0 0         return $result if (delete $result->{use_this}); #<-- temp hack
768            
769             # we don't actually care about the new record, so we simply give the store back
770             # the row it gave to us. We have to make sure that pk (primary key) is set to
771             # something or else it will throw an error (update: bypass this failsafe if more
772             # than one row was provided in the request, that is, if its an array instead of
773             # a hash)
774 0 0         $rows->{$self->record_pk} = 'dummy-key' if (ref($rows) eq 'HASH');
775            
776             # If the id of the new record was provided in the response, we'll use it:
777 0 0 0       $rows = $result->{rows} if (ref($result) and defined $result->{rows} and defined $result->{rows}->{$self->record_pk});
      0        
778            
779             # Use the provided rows if its an array. Assume the record_pk is provided in each row:
780 0 0 0       $rows = $result->{rows} if (ref($result) and ref($result->{rows}) eq 'ARRAY');
781            
782 0 0 0       if (ref($result) and defined $result->{success} and defined $result->{msg}) {
      0        
783 0           $result->{rows} = $rows;
784 0 0         if ($result->{success}) {
785 0           $result->{success} = \1;
786             }
787             else {
788 0           $result->{success} = \0;
789             }
790 0           return $result;
791             }
792            
793            
794 0 0 0       if ($result and not (ref($result) and $result->{success} == 0 )) {
      0        
795             return {
796 0           success => \1,
797             msg => 'Create Succeeded',
798             rows => $rows
799             }
800             }
801            
802 0 0         if(ref($result) eq 'HASH') {
803 0           $result->{success} = \0;
804 0 0         $result->{msg} = 'Create Failed' unless (defined $result->{msg});
805 0           die $result->{msg};
806             }
807            
808 0           die 'Create Failed';
809             }
810              
811              
812              
813             sub destroy {
814 0     0 0   my $self = shift;
815            
816 0           $self->parent_module->enforce_permission;
817            
818 0           my $params = $self->c->req->params;
819 0           my $rows = $self->json->decode($params->{rows});
820 0           delete $params->{rows};
821            
822 0 0         my $result = $self->destroy_handler->call($rows) or return {
823             success => \0,
824             msg => 'destroy failed'
825             };
826            
827 0 0 0       return $result if (ref($result) eq 'HASH' and $result->{success});
828            
829             return {
830 0           success => \1,
831             msg => 'destroy success'
832             };
833             }
834              
835              
836             has 'getStore' => ( is => 'ro', lazy => 1, default => sub {
837             my $self = shift;
838             return $self->JsonStore;
839             });
840              
841              
842 0     0 0   sub getStore_code { join('','Ext.StoreMgr.lookup("',(shift)->storeId,'")') }
843 0     0 0   sub getStore_func { RapidApp::JSONFunc->new(raw=>1, func=>(shift)->getStore_code) }
844 0     0 0   sub store_load_code { join('',(shift)->getStore_code,'.load()') }
845              
846             #has 'getStore_code' => ( is => 'ro', lazy_build => 1 );
847             #sub _build_getStore_code {
848             # my $self = shift;
849             # scream_color(RED,$self->storeId);
850             # return 'Ext.StoreMgr.lookup("' . $self->storeId . '")';
851             #}
852             #
853             #has 'getStore_func' => ( is => 'ro', lazy_build => 1 );
854             #sub _build_getStore_func {
855             # my $self = shift;
856             # return RapidApp::JSONFunc->new(
857             # raw => 1,
858             # func => $self->getStore_code
859             # );
860             #}
861             #
862             #has 'store_load_code' => ( is => 'ro', lazy_build => 1 );
863             #sub _build_store_load_code {
864             # my $self = shift;
865             # return $self->getStore_code . '.load()';
866             #}
867              
868             ## TODO: get a more reliable way to access the store - use local references and scope
869             ## instead of relying on a global ID:
870             #has 'store_load_fn' => ( is => 'ro', isa => 'RapidApp::JSONFunc', lazy => 1, default => sub {
871             # my $self = shift;
872             # return RapidApp::JSONFunc->new( raw => 1, func =>
873             # 'function() {' .
874             # 'var storeId = "' . $self->storeId . '";' .
875             # 'var storeByLookup = Ext.StoreMgr.lookup(storeId);' .
876             # 'if(storeByLookup) { storeByLookup.load(); }' .
877             # '}'
878             # );
879             #});
880              
881              
882             has 'store_api' => ( is => 'ro', lazy => 1, default => sub {
883             my $self = shift;
884            
885             my $api = {};
886            
887             $api->{read} = $self->suburl('/read');
888             $api->{update} = $self->suburl('/update') if (defined $self->update_handler);
889             $api->{create} = $self->suburl('/create') if (defined $self->create_handler);
890             $api->{destroy} = $self->suburl('/destroy') if (defined $self->destroy_handler);
891            
892             return $api;
893             });
894              
895              
896              
897             has 'store_writer' => ( is => 'ro', lazy => 1, default => sub {
898             my $self = shift;
899            
900             return undef unless (
901             defined $self->update_handler or
902             defined $self->create_handler or
903             defined $self->destroy_handler
904             );
905            
906             my $writer = RapidApp::JSONFunc->new(
907             func => 'new Ext.data.JsonWriter',
908             parm => {
909             encode => \1,
910             #writeAllFields => \1
911             });
912            
913             return $writer;
914             });
915              
916              
917              
918              
919              
920              
921             #### --------------------- ####
922              
923              
924 5     5   39 no Moose;
  5         10  
  5         48  
925             #__PACKAGE__->meta->make_immutable;
926             1;