File Coverage

blib/lib/RapidApp/TableSpec.pm
Criterion Covered Total %
statement 59 216 27.3
branch 6 80 7.5
condition 2 22 9.0
subroutine 12 25 48.0
pod 0 18 0.0
total 79 361 21.8


line stmt bran cond sub pod time code
1             package RapidApp::TableSpec;
2 5     5   522 use strict;
  5         11  
  5         129  
3 5     5   23 use Moose;
  5         9  
  5         31  
4             with 'MooseX::Traits';
5              
6             # This configuration class defines behaviors of tables and
7             # columns in a general way that can be used in different places
8              
9 5     5   27959 use RapidApp::Util qw(:all);
  5         12  
  5         1746  
10 5     5   2049 use RapidApp::TableSpec::Column;
  5         17  
  5         13401  
11              
12             our $VERSION = '0.1';
13              
14             sub BUILD {
15 230     230 0 521 my $self = shift;
16 230         6087 $self->add_onrequest_columns_mungers( $self->column_permissions_roles_munger );
17             }
18              
19             around BUILDARGS => sub {
20             my $orig = shift;
21             my $class = shift;
22             my %params = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
23            
24             # -- New: handle rogue name values, like ScalarRefs which DBIC sometimes uses
25             # for the ->table attr of Result classes, and also normalize values
26             # ** note: this should no longer be needed since we added _table_name_safe()
27             if(my $table = $params{name}) {
28             $table = $$table if (ref($table)||'' eq 'SCALAR');
29             $table =~ s/("|')//g;
30             $table = (split(/\./,$table,2))[1] || $table; #<-- get 'table' for both 'db.table' and 'table' format
31             $params{name} = $table;
32             }
33             # --
34            
35             return $class->$orig(%params);
36             };
37              
38              
39              
40             has 'ResultClass' => ( is => 'ro', isa => 'Str' );
41              
42             has 'name' => ( is => 'ro', isa => 'Str', required => 1 );
43             has 'title' => ( is => 'ro', isa => 'Maybe[Str]', default => undef );
44             has 'iconCls' => ( is => 'ro', isa => 'Maybe[Str]', default => undef );
45              
46              
47             has 'header_prefix' => ( is => 'ro', isa => 'Maybe[Str]', default => undef );
48              
49             # Hash of CodeRefs to programatically change Column properties
50             has 'column_property_transforms' => ( is => 'ro', isa => 'Maybe[HashRef[CodeRef]]', default => undef );
51              
52             # Hash of static changes to apply to named properties of all Columns
53             has 'column_properties' => ( is => 'ro', isa => 'Maybe[HashRef]', default => undef );
54              
55             # Hash of static properties initially applied to all Columns (if not already set)
56             has 'default_column_properties' => ( is => 'ro', isa => 'Maybe[HashRef]', default => undef );
57              
58             has 'profile_definitions' => ( is => 'ro', isa => 'Maybe[HashRef]', default => undef );
59              
60             has 'column_order' => ( is => 'ro', isa => 'ArrayRef[Str]', default => sub {[]} );
61              
62             has 'columns' => (
63             traits => ['Hash'],
64             is => 'ro',
65             isa => 'HashRef[RapidApp::TableSpec::Column]',
66             default => sub { {} },
67             handles => {
68             apply_columns => 'set',
69             get_column => 'get',
70             has_column => 'exists',
71             column_list => 'values',
72             num_columns => 'count',
73             delete_column => 'delete'
74             }
75             );
76             around 'apply_columns' => sub {
77             my $orig = shift;
78             my $self = shift;
79             my %cols = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
80              
81             my $def = $self->default_column_properties;
82             if ($def) {
83             foreach my $Column (values %cols) {
84             $Column->set_properties_If($def);
85             }
86             }
87            
88             push @{$self->column_order}, grep { ! $self->columns->{$_} } keys %cols;
89              
90             $self->$orig(%cols);
91             $self->prune_invalid_columns;
92             };
93             around 'column_list' => sub {
94             my $orig = shift;
95             my $self = shift;
96             # Force column_list to go through get_column so its logic gets called:
97             return grep { $_ = $self->get_column($_) } $self->updated_column_order;
98             };
99             around 'get_column' => sub {
100             my $orig = shift;
101             my $self = shift;
102             my $name = shift;
103             my $Column = $self->$orig($name) || return undef;
104            
105             return $Column unless (
106             defined $self->column_property_transforms or (
107             defined $self->column_properties and
108             defined $self->column_properties->{$Column->name}
109             )
110             );
111            
112             my $trans = $self->column_property_transforms;
113             my $cur_props = $Column->all_properties_hash;
114             my %change_props = ();
115            
116             foreach my $prop (keys %$trans) {
117             local $_ = $cur_props->{$prop};
118             $change_props{$prop} = $trans->{$prop}->($cur_props);
119             delete $change_props{$prop} unless (defined $change_props{$prop});
120             }
121            
122             %change_props = ( %change_props, %{ $self->column_properties->{$Column->name} } ) if (
123             defined $self->column_properties and
124             defined $self->column_properties->{$Column->name}
125             );
126            
127             return $Column->copy(%change_props);
128             };
129              
130              
131              
132             has 'limit_columns' => ( is => 'rw', isa => 'Maybe[ArrayRef[Str]]', default => undef, trigger => \&prune_invalid_columns );
133             has 'exclude_columns' => ( is => 'rw', isa => 'Maybe[ArrayRef[Str]]', default => undef, trigger => \&prune_invalid_columns );
134              
135             sub prune_invalid_columns {
136 1094     1094 0 1536 my $self = shift;
137            
138 1094         1712 my @remove_cols = ();
139            
140 1094 50 33     26171 if (defined $self->limit_columns and scalar @{ $self->limit_columns } > 0) {
  0         0  
141 0         0 my %map = map { $_ => 1 } @{ $self->limit_columns };
  0         0  
  0         0  
142 0         0 push @remove_cols, grep { not defined $map{$_} } keys %{ $self->columns };
  0         0  
  0         0  
143             }
144            
145 1094 50 33     23659 if (defined $self->exclude_columns and scalar @{ $self->exclude_columns } > 0) {
  0         0  
146 0         0 my %map = map { $_ => 1 } @{ $self->exclude_columns };
  0         0  
  0         0  
147 0         0 push @remove_cols, grep { defined $map{$_} } keys %{ $self->columns };
  0         0  
  0         0  
148             }
149            
150 1094         2107 foreach my $remove (@remove_cols) {
151 0         0 delete $self->columns->{$remove};
152             }
153            
154 1094         2952 $self->updated_column_order;
155             }
156              
157             sub updated_column_order {
158 3292     3292 0 4575 my $self = shift;
159 3292         4296 my %seen = ();
160             # Prune out duplciates and columns not in $self->columns
161 3292 50       4329 @{$self->column_order} = grep { !$seen{$_}++ and $self->columns->{$_} } @{$self->column_order};
  3292         67216  
  15113         310057  
  3292         68873  
162             # Append any missing columns to the end (shouldn't be any)
163 3292         4948 push @{$self->column_order}, grep { !$seen{$_} } keys %{$self->columns};
  3292         65511  
  12599         19158  
  3292         65049  
164 3292         4748 return @{$self->column_order};
  3292         66766  
165             }
166              
167              
168             sub get_column_order_index {
169 0     0 0 0 my $self = shift;
170 0         0 my $column = shift;
171 0         0 my $i = 0;
172 0         0 for my $col ($self->updated_column_order) {
173 0 0       0 return $i if ($col eq $column);
174 0         0 $i++;
175             }
176 0         0 die "get_column_order_index(): column name '$column' not found";
177             }
178              
179             sub set_column_order_before {
180 0     0 0 0 my $self = shift;
181 0         0 my $colname = shift;
182 0         0 my @cols = @_;
183 0         0 my $offset = $self->get_column_order_index($colname);
184 0         0 return $self->set_column_order($offset,@cols);
185             }
186              
187             sub set_column_order_after {
188 0     0 0 0 my $self = shift;
189 0         0 my $colname = shift;
190 0         0 my @cols = @_;
191 0         0 my $offset = $self->get_column_order_index($colname);
192 0         0 return $self->set_column_order(++$offset,@cols);
193             }
194              
195             sub set_column_orderIf {
196 0     0 0 0 my $self = shift;
197 0         0 my $offset = shift;
198 0         0 my @cols = @_;
199 0 0 0     0 @cols = @{$_[0]} if (scalar @_ == 1 and ref($_[0]) eq 'ARRAY');
  0         0  
200 0         0 return $self->set_column_order($offset, grep { exists $self->columns->{$_} } @cols);
  0         0  
201             }
202              
203             sub set_column_order {
204 0     0 0 0 my $self = shift;
205 0         0 my $offset = shift;
206 0 0       0 die "First argument to set_column_order must be an index/offset" unless ($offset =~ /^\d+$/);
207 0         0 my @cols = @_;
208 0 0 0     0 @cols = @{$_[0]} if (scalar @_ == 1 and ref($_[0]) eq 'ARRAY');
  0         0  
209            
210 0         0 my %seen = ();
211 0   0     0 !$seen{$_}++ or die "set_column_order(): column name specified more than once ($_)" for (@cols);
212 0   0     0 $self->has_column($_) or die "set_column_order(): cannot set the order of non-existant columns ($_)" for (@cols);
213            
214 0         0 my %cols_map = map { $_ => 1 } @cols;
  0         0  
215            
216             #prune out the new columns from the current order:
217 0         0 @{$self->column_order} = grep { !$cols_map{$_} } @{$self->column_order};
  0         0  
  0         0  
  0         0  
218            
219 0 0       0 if ($offset < scalar @{$self->column_order}) {
  0         0  
220             # Add them back in at the new offset/index:
221 0         0 splice(@{$self->column_order},$offset,0,@cols);
  0         0  
222             }
223             else {
224             # offset is at or past the end of the array, ignore it and just append:
225 0         0 push @{$self->column_order}, @cols;
  0         0  
226             }
227            
228             # Just to be safe:
229 0         0 $self->updated_column_order;
230             }
231              
232 0     0 0 0 sub column_names { (shift)->column_names_ordered }
233             sub column_names_ordered {
234 92     92 0 191 my $self = shift;
235 92         360 return map { $_->name } $self->column_list;
  781         16286  
236             }
237              
238              
239             sub columns_properties_limited {
240 92     92 0 200 my $self = shift;
241 92         213 my @props = @_;
242 92         448 $self->updated_column_order;
243 92         441 return { map { $_->name => $_->properties_limited(@props) } $self->column_list };
  781         21429  
244             }
245              
246              
247             sub add_columns {
248 1094     1094 0 1747 my $self = shift;
249 1094         2060 my @cols = (@_);
250            
251 1094         1648 my @added = ();
252            
253 1094         1858 foreach my $col (@cols) {
254 1094         1393 my $Column;
255 1094 50       2456 $Column = $col if (ref($col) eq 'RapidApp::TableSpec::Column');
256 1094 50       1983 unless ($Column) {
257 1094 50       29987 $col->{profile_definitions} = $self->profile_definitions if ($self->profile_definitions);
258 1094         28306 $Column = RapidApp::TableSpec::Column->new($col);
259 1094         3256 $Column->set_properties($col);
260             }
261            
262             #die "A column named " . $Column->name . ' already exists.' if (defined $self->has_column($Column->name));
263            
264 1094         25070 $self->apply_columns( $Column->name => $Column );
265 1094         2390 push @added, $Column;
266             }
267            
268 1094         2882 $self->update_column_permissions_roles_code;
269 1094         20045 return @added;
270             }
271              
272              
273             sub applyIf_column_properties {
274 0     0 0 0 my $self = shift;
275 0 0       0 my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
276 0         0 my $hash = \%new;
277              
278 0         0 my $pruned = { map { $_ => $hash->{$_} } grep { $self->get_column($_) } keys %$hash };
  0         0  
  0         0  
279            
280 0         0 return $self->apply_column_properties($pruned);
281             }
282              
283             sub apply_column_properties {
284 0     0 0 0 my $self = shift;
285            
286 0 0       0 my %new = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
287 0         0 my $hash = \%new;
288            
289 0         0 foreach my $col (keys %$hash) {
290 0 0       0 my $Column = $self->get_column($col) or die "apply_column_properties failed - no such column '$col'";
291 0         0 $Column->set_properties($hash->{$col});
292             }
293            
294 0         0 $self->update_column_permissions_roles_code;
295             }
296              
297              
298              
299             sub copy {
300 0     0 0 0 my $self = shift;
301 0 0       0 my %opts = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
302            
303 0         0 my %attr = ();
304 0         0 my %other = ();
305            
306 0         0 foreach my $opt (keys %opts) {
307 0 0       0 if ($self->meta->find_attribute_by_name($opt)) {
308 0         0 $attr{$opt} = $opts{$opt};
309             }
310             else {
311 0         0 $other{$opt} = $opts{$opt};
312             }
313             }
314            
315             # Need to use Clone::clone to ensure a deep copy. Discovered that with
316             # clone_object alone, deeper data scructures, such as 'columns' attribute,
317             # were only copied by reference, and not be deep data
318 0         0 my $Copy = $self->meta->clone_object(Clone::clone($self),%attr);
319            
320 0         0 foreach my $key (keys %other) {
321 0 0       0 $Copy->$key($other{$key}) if ($Copy->can($key));
322             }
323            
324             # If column property transforms (name) was supplied, use it to transform
325             # limit/exclude columns:
326 0 0 0     0 if($opts{column_property_transforms} and $opts{column_property_transforms}{name}) {
327 0         0 my $sub = $opts{column_property_transforms}{name};
328            
329 0 0       0 if($Copy->limit_columns) {
330 0         0 my @limit = map { $sub->() } @{ $Copy->limit_columns };
  0         0  
  0         0  
331 0 0       0 $Copy->limit_columns(\@limit) if (scalar @limit > 0);
332             }
333            
334 0 0       0 if ($Copy->exclude_columns) {
335 0         0 my @exclude = map { $sub->() } @{ $Copy->exclude_columns };
  0         0  
  0         0  
336 0 0       0 $Copy->exclude_columns(\@exclude) if (scalar @exclude > 0);
337             }
338             }
339            
340 0         0 return $Copy;
341             }
342              
343             sub add_columns_from_TableSpec {
344 0     0 0 0 my $self = shift;
345 0         0 my $TableSpec = shift;
346            
347 0         0 my @added = ();
348 0         0 push @added, $self->add_columns($_) for ($TableSpec->column_list);
349            
350             # Apply foreign TableSpec's limit/exclude columns:
351 0         0 my %seen = ();
352 0         0 my @limit = ();
353 0 0       0 push @limit, @{ $self->limit_columns } if ($self->limit_columns);
  0         0  
354 0 0       0 push @limit, @{ $TableSpec->limit_columns } if ($TableSpec->limit_columns);
  0         0  
355 0         0 @limit = grep { not $seen{$_}++ } @limit;
  0         0  
356 0 0       0 $self->limit_columns(\@limit) if (scalar @limit > 0);
357            
358 0         0 %seen = ();
359 0         0 my @exclude = ();
360 0 0       0 push @exclude, @{ $self->exclude_columns } if ($self->exclude_columns);
  0         0  
361 0 0       0 push @exclude, @{ $TableSpec->exclude_columns } if ($TableSpec->exclude_columns);
  0         0  
362 0         0 @exclude = grep { not $seen{$_}++ } @exclude;
  0         0  
363 0 0       0 $self->exclude_columns(\@exclude) if (scalar @exclude > 0);
364            
365 0         0 $self->updated_column_order;
366 0         0 return @added;
367             }
368              
369              
370             # Designed to work with DataStore2: if defined, gets added as an
371             # onrequest_columns_munger to DataStore2-based modules that are
372             # configured to use this TableSpec:
373             has 'onrequest_columns_mungers' => (
374             traits => [ 'Array' ],
375             is => 'ro',
376             isa => 'ArrayRef[RapidApp::Handler]',
377             default => sub { [] },
378             handles => {
379             all_onrequest_columns_mungers => 'uniq',
380             add_onrequest_columns_mungers => 'push',
381             insert_onrequest_columns_mungers => 'unshift',
382             has_no_onrequest_columns_mungers => 'is_empty',
383             }
384             );
385              
386              
387             has 'column_permissions_roles_munger' => (
388             is => 'ro',
389             isa => 'RapidApp::Handler',
390             default => sub { RapidApp::Handler->new( code => sub {} ) }
391             );
392              
393              
394             has 'roles_permissions_columns_map' => ( is => 'rw', isa => 'HashRef', default => sub {{}} );
395              
396             sub update_column_permissions_roles_code {
397 1094     1094 0 1494 my $self = shift;
398            
399             # NOT IN USE
400 1094         1421 return;
401            
402 0           my $roles = {};
403            
404 0           foreach my $Column ($self->column_list) {
405 0 0         $Column->permission_roles or next;
406            
407 0           foreach my $perm ( keys %{ $Column->permission_roles } ) {
  0            
408 0           foreach my $role ( @{ $Column->permission_roles->{$perm} } ) {
  0            
409 0 0 0       die "Role names cannot contain spaces ('$role')" if (not ref($role) and $role =~ /\s+/);
410 0           my $rolespec = $role;
411 0 0         $rolespec = join(' ',@$role) if (ref($role) eq 'ARRAY');
412 0 0         $roles->{$rolespec} = {} unless ($roles->{$rolespec});
413 0 0         $roles->{$rolespec}{$perm} = [] unless ($roles->{$rolespec}{$perm});
414 0           push @{ $roles->{$rolespec}{$perm} }, $Column->name;
  0            
415             }
416             }
417             }
418            
419 0           $self->roles_permissions_columns_map($roles);
420            
421 0 0   0     return $self->column_permissions_roles_munger->code(sub {}) unless (scalar(keys %$roles) > 0);
422             return $self->column_permissions_roles_munger->code(sub {
423 0     0     my $columns = shift;
424 0           return $self->apply_permission_roles_to_datastore_columns($columns);
425 0           });
426             }
427              
428              
429             # This code not in use -
430             sub apply_permission_roles_to_datastore_columns {
431 0     0 0   my $self = shift;
432 0           my $columns = shift;
433            
434 0           my $c = RapidApp->active_request_context;
435             #delete $columns->{creator}->{editor} unless ($c->check_user_roles('admin'));
436            
437 0           my $map = $self->roles_permissions_columns_map;
438            
439 0           foreach my $role (keys %$map) {
440 0 0         if ($c->check_user_roles(split(/\s+/,$role))) {
441             # Any code that would need to be called for the positive condition would go here
442            
443             }
444             else {
445            
446             #CREATE:
447 0 0         if ($map->{$role}->{create}) {
    0          
    0          
    0          
448            
449            
450             }
451             #READ:
452             elsif ($map->{$role}->{read}) {
453            
454            
455             }
456             #UPDATE:
457             elsif ($map->{$role}->{update}) {
458 0           my $list = $map->{$role}->{update};
459 0 0         $list = [ $list ] unless (ref($list));
460 0           foreach my $colname (@$list) {
461 0           delete $columns->{$colname}->{editor};
462             }
463             }
464             #DESTROY
465             elsif ($map->{$role}->{destroy}) {
466            
467            
468             }
469            
470            
471             }
472            
473            
474             }
475            
476             # TODO
477            
478             #scream($self->roles_permissions_columns_map);
479             }
480              
481             # If TableSpec should be cached, then here we need a Cache::Cache
482             # object, which is given on DbicLnk if use_cache is on.
483             has 'cache' => ( is => 'ro', predicate => 'has_cache' );
484              
485 5     5   49 no Moose;
  5         11  
  5         41  
486             __PACKAGE__->meta->make_immutable;
487             1;