File Coverage

blib/lib/RapidApp/DBIC/Component/TableSpec.pm
Criterion Covered Total %
statement 323 456 70.8
branch 113 238 47.4
condition 85 205 41.4
subroutine 31 133 23.3
pod 0 24 0.0
total 552 1056 52.2


line stmt bran cond sub pod time code
1             package RapidApp::DBIC::Component::TableSpec;
2             #use base 'DBIx::Class';
3             # this is for Attribute::Handlers:
4             require base; base->import('DBIx::Class');
5              
6 5     5   98284 use strict;
  5         15  
  5         173  
7 5     5   31 use warnings;
  5         11  
  5         169  
8              
9 5     5   501 use Sub::Name qw/subname/;
  5         644  
  5         292  
10              
11             # DBIx::Class Component: ties a RapidApp::TableSpec object to
12             # a Result class for use in configuring various modules that
13             # consume/use a DBIC Source
14              
15 5     5   536 use RapidApp::Util qw(:all);
  5         14  
  5         3048  
16              
17 5     5   2078 use RapidApp::TableSpec;
  5         18  
  5         249  
18 5     5   7836 use RapidApp::Module::DbicCombo;
  5         22  
  5         362  
19 5     5   39 use Module::Runtime;
  5         9  
  5         67  
20              
21             #__PACKAGE__->load_components(qw/IntrospectableM2M/);
22              
23             __PACKAGE__->load_components('+RapidApp::DBIC::Component::VirtualColumnsExt');
24              
25             __PACKAGE__->mk_classdata( 'TableSpec' );
26             __PACKAGE__->mk_classdata( 'TableSpec_rel_columns' );
27              
28             __PACKAGE__->mk_classdata( 'TableSpec_cnf' );
29             __PACKAGE__->mk_classdata( 'TableSpec_built_cnf' );
30              
31             # See default profile definitions in RapidApp::TableSpec::Column
32             my $default_data_type_profiles = {
33             text => [ 'bigtext' ],
34             mediumtext => [ 'bigtext' ],
35             longtext => [ 'bigtext' ],
36             tinytext => [ 'text' ],
37             smalltext => [ 'text' ],
38             varchar => [ 'text' ],
39             char => [ 'text' ],
40             nvarchar => [ 'text' ],
41             nchar => [ 'text' ],
42             float => [ 'number' ],
43             integer => [ 'number', 'int' ],
44             tinyint => [ 'number', 'int' ],
45             smallint => [ 'number', 'int' ],
46             mediumint => [ 'number', 'int' ],
47             bigint => [ 'number', 'int' ],
48             decimal => [ 'number' ],
49             numeric => [ 'number' ],
50             double => [ 'number' ],
51             'double precision' => [ 'number' ],
52             datetime => [ 'datetime' ],
53             timestamp => [ 'datetime' ],
54             date => [ 'date' ],
55             blob => [ 'blob' ],
56             longblob => [ 'blob' ],
57             mediumblob => [ 'blob' ],
58             tinyblob => [ 'blob' ],
59             smallblob => [ 'blob' ],
60             binary => [ 'blob' ],
61             varbinary => [ 'blob' ],
62             year => [ 'otherdate' ],
63             tsvector => [ 'bigtext','unsearchable','virtual_source' ], #<-- postgres-specific
64             boolean => ['bool'],
65             ipaddr => ['unsearchable'] #<-- postgres-specific
66             };
67             __PACKAGE__->mk_classdata( 'TableSpec_data_type_profiles' );
68             __PACKAGE__->TableSpec_data_type_profiles({ %$default_data_type_profiles });
69              
70              
71             ## Sets up many_to_many along with TableSpec m2m multi-relationship column
72             sub TableSpec_m2m {
73 2     2 0 6 my $self = shift;
74 2         7 my ($m2m,$local_rel,$remote_rel) = @_;
75            
76 2 50       13 $self->is_TableSpec_applied and
77             die "TableSpec_m2m must be called before apply_TableSpec!";
78            
79 2 50       300 $self->has_column($m2m) and die "'$m2m' is already defined as a column.";
80 2 50       201 $self->has_relationship($m2m) and die "'$m2m' is already defined as a relationship.";
81              
82 2 50       539 my $rinfo = $self->relationship_info($local_rel) or die "'$local_rel' relationship not found";
83 2         214 eval('require ' . $rinfo->{class});
84            
85             die "m2m bridge relationship '$local_rel' is not a multi relationship"
86 2 50       15 unless ($rinfo->{attrs}->{accessor} eq 'multi');
87            
88 2         50 my $rrinfo = $rinfo->{class}->relationship_info($remote_rel);
89 2 50       95 unless($rrinfo) {
90             # Note: we're not dying here because this is known to happen when called from Schema::Loader
91             # and we don't want that to fail. It is not known to fail during normal operation. TODO/FIXME
92 0         0 warn "TableSpec_m2m(): unable to resolve remote rel '$remote_rel' -- falling back to many_to_many\n";
93 0         0 return $self->many_to_many($m2m,$local_rel,$remote_rel);
94             }
95            
96 2         10 Module::Runtime::require_module($rrinfo->{class});
97            
98 2         54 $rinfo->{table} = &_table_name_safe($rinfo->{class}->table);
99 2         19 $rrinfo->{table} = &_table_name_safe($rrinfo->{class}->table);
100            
101 2         15 $rinfo->{cond_info} = $self->parse_relationship_cond($rinfo->{cond});
102 2         7 $rrinfo->{cond_info} = $self->parse_relationship_cond($rrinfo->{cond});
103            
104             #
105             #my $sql = '(' .
106             # # SQLite Specific:
107             # #'SELECT(GROUP_CONCAT(flags.flag,", "))' .
108             #
109             # # MySQL Sepcific:
110             # #'SELECT(GROUP_CONCAT(flags.flag SEPARATOR ", "))' .
111             #
112             # # Generic (MySQL & SQLite):
113             # 'SELECT(GROUP_CONCAT(`' . $rrinfo->{table} . '`.`' . $rrinfo->{cond_info}->{foreign} . '`))' .
114             #
115             # ' FROM `' . $rinfo->{table} . '`' .
116             # ' JOIN `' . $rrinfo->{table} . '` `' . $rrinfo->{table} . '`' .
117             # ' ON `' . $rinfo->{table} . '`.`' . $rrinfo->{cond_info}->{self} . '`' .
118             # ' = `' . $rrinfo->{table} . '`.`' . $rrinfo->{cond_info}->{foreign} . '`' .
119             # #' ON customers_to_flags.flag = flags.flag' .
120             # ' WHERE `' . $rinfo->{cond_info}->{foreign} . '` = ' . $rel . '.' . $cond_data->{self} .
121             #')';
122              
123             # Create a relationship exactly like the the local bridge relationship, adding
124             # the 'm2m_attrs' attribute which will be used later on to setup the special,
125             # m2m-specific multi-relationship column properties (renderer, editor, and to
126             # trigger proxy m2m updates in DbicLink2):
127             $self->add_relationship(
128             $m2m,
129             $rinfo->{class},
130             $rinfo->{cond},
131 2         7 {%{$rinfo->{attrs}}, m2m_attrs => {
  2         38  
132             remote_rel => $remote_rel,
133             rinfo => $rinfo,
134             rrinfo => $rrinfo
135             }}
136             );
137            
138             # -- Add a normal many_to_many bridge so we have the many_to_many sugar later on:
139             # (we use 'set_$rel' in update_records in DbicLink2)
140             local $ENV{DBIC_OVERWRITE_HELPER_METHODS_OK} = 1
141 2 50       824 unless (exists $ENV{DBIC_OVERWRITE_HELPER_METHODS_OK});
142 2         26 $self->many_to_many(@_);
143             #$self->apply_m2m_sugar(@_);
144             # --
145             }
146              
147             ## sugar copied from many_to_many (DBIx::Class::Relationship::ManyToMany),
148             ## but only sets up add_$rel and set_$rel and won't overwrite existing subs (safer)
149             #sub apply_m2m_sugar {
150             # my ($class, $meth, $rel, $f_rel, $rel_attrs) = @_;
151             #
152             # my $set_meth = "set_${meth}";
153             # my $add_meth = "add_${meth}";
154             #
155             # $class->can($set_meth) and
156             # die "m2m: set method '$set_meth' is already defined in (" . ref($class) . ")";
157             #
158             # $class->can($add_meth) and
159             # die "m2m: add method '$add_meth' is already defined in (" . ref($class) . ")";
160             #
161             # my $add_meth_name = join '::', $class, $add_meth;
162             # *$add_meth_name = subname $add_meth_name, sub {
163             # my $self = shift;
164             # @_ > 0 or $self->throw_exception(
165             # "${add_meth} needs an object or hashref"
166             # );
167             # my $source = $self->result_source;
168             # my $schema = $source->schema;
169             # my $rel_source_name = $source->relationship_info($rel)->{source};
170             # my $rel_source = $schema->resultset($rel_source_name)->result_source;
171             # my $f_rel_source_name = $rel_source->relationship_info($f_rel)->{source};
172             # my $f_rel_rs = $schema->resultset($f_rel_source_name)->search({}, $rel_attrs||{});
173             #
174             # my $obj;
175             # if (ref $_[0]) {
176             # if (ref $_[0] eq 'HASH') {
177             # $obj = $f_rel_rs->find_or_create($_[0]);
178             # } else {
179             # $obj = $_[0];
180             # }
181             # } else {
182             # $obj = $f_rel_rs->find_or_create({@_});
183             # }
184             #
185             # my $link_vals = @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {};
186             # my $link = $self->search_related($rel)->new_result($link_vals);
187             # $link->set_from_related($f_rel, $obj);
188             # $link->insert();
189             # return $obj;
190             # };
191             #
192             # my $set_meth_name = join '::', $class, $set_meth;
193             # *$set_meth_name = subname $set_meth_name, sub {
194             # my $self = shift;
195             # @_ > 0 or $self->throw_exception(
196             # "{$set_meth} needs a list of objects or hashrefs"
197             # );
198             # my @to_set = (ref($_[0]) eq 'ARRAY' ? @{ $_[0] } : @_);
199             # # if there is a where clause in the attributes, ensure we only delete
200             # # rows that are within the where restriction
201             # if ($rel_attrs && $rel_attrs->{where}) {
202             # $self->search_related( $rel, $rel_attrs->{where},{join => $f_rel})->delete;
203             # } else {
204             # $self->search_related( $rel, {} )->delete;
205             # }
206             # # add in the set rel objects
207             # $self->$add_meth($_, ref($_[1]) ? $_[1] : {}) for (@to_set);
208             # };
209             #}
210             ## --
211              
212             sub is_TableSpec_applied {
213 56     56 0 124 my $self = shift;
214             return (
215             defined $self->TableSpec_cnf and
216             defined $self->TableSpec_cnf->{apply_TableSpec_timestamp}
217 56   33     1404 );
218             }
219              
220             sub apply_TableSpec {
221 54     54 0 155 my $self = shift;
222 54 50       276 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
223            
224             # ignore/return if apply_TableSpec has already been called:
225 54 50       349 return if $self->is_TableSpec_applied;
226            
227             # make sure _virtual_columns and _virtual_columns_order get initialized
228 54         9639 $self->add_virtual_columns();
229              
230            
231             $self->TableSpec_data_type_profiles(
232 0 0       0 %{ $self->TableSpec_data_type_profiles || {} },
233 0         0 %{ delete $opt{TableSpec_data_type_profiles} }
234 54 50       186 ) if ($opt{TableSpec_data_type_profiles});
235            
236 54         379 $self->TableSpec($self->create_result_TableSpec($self,%opt));
237            
238 54         2573 $self->TableSpec_rel_columns({});
239 54         1745 $self->TableSpec_cnf({});
240 54         1914 $self->TableSpec_built_cnf(undef);
241            
242 54         1253 $self->apply_row_methods();
243            
244             # Just doing this to ensure we're initialized:
245 54         447 $self->TableSpec_set_conf( apply_TableSpec_timestamp => time );
246            
247             # --- Set some base defaults here:
248 54         1206 my $table = &_table_name_safe($self->table);
249 54         1833 my ($pri) = ($self->primary_columns,$self->columns); #<-- first primary col, or first col
250             $self->TableSpec_set_conf(
251             display_column => $pri,
252             title => $table,
253            
254             # --
255             # New: initialize the columns cnf key early. It doesn't even need all
256             # the columns (just at least one -- we're just doing the base columns
257             # and not bothering with relationships + virtual columns). This is
258             # just about getting the Hash defined so that later calls will update
259             # this hash rather than create a new one, which can get lost in certain
260             # situations (such as a Result Class that loads the TableSpec component
261             # in-line but does not apply any column configs).
262             # This was needed added after the recent prelim TableSpec_cnf refactor (in v0.99030)
263             # which is a temp/in-between change that consolidates storage of column
264             # configs internally while still preserving the original API for now.
265             # Yes, this is ugly/hackish but will go away as soon as the full-blown,
266             # long-planned TableSpec refactor is undertaken...
267 54         917 columns => { map { $_ => {} } $self->columns }
  313         1504  
268             # --
269             );
270             # ---
271            
272 54         3225 return $self;
273             }
274              
275             sub create_result_TableSpec {
276 54     54 0 128 my $self = shift;
277 54         97 my $ResultClass = shift;
278 54 50       193 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
279            
280 54         633 my $table = &_table_name_safe($ResultClass->table);
281              
282 54         1689 my $TableSpec = RapidApp::TableSpec->new(
283             name => $table,
284             %opt
285             );
286              
287 54         1260 my $data_types = $self->TableSpec_data_type_profiles;
288            
289             ## WARNING! This logic overlaps with logic further down (in default_TableSpec_cnf_columns)
290 54         2880 foreach my $col ($ResultClass->columns) {
291 313         2100 my $info = $ResultClass->column_info($col);
292 313         32437 my @profiles = ();
293            
294 313 100       1139 push @profiles, $info->{is_nullable} ? 'nullable' : 'notnull';
295 313 100       777 push @profiles, 'autoinc' if ($info->{is_auto_increment});
296            
297 313   50     1303 my $type_profile = $data_types->{$info->{data_type}} || ['text'];
298            
299             # -- PostgreSQL override until array columns are supported (Github Issue #55):
300             $type_profile = ['unsearchable','virtual_source'] if (
301 313 50       866 $info->{data_type} =~ /\[/ #<-- if the data_type contains a square backect, i.e. 'text[]'
302             );
303             # --
304            
305 313 50       725 $type_profile = [ $type_profile ] unless (ref $type_profile);
306 313         606 push @profiles, @$type_profile;
307            
308 313         1429 $TableSpec->add_columns( { name => $col, profiles => \@profiles } );
309             }
310            
311 54         1627 return $TableSpec;
312             }
313              
314              
315             sub get_built_Cnf {
316 1056     1056 0 2285 my $self = shift;
317            
318 1056 100       22127 $self->TableSpec_build_cnf unless ($self->TableSpec_built_cnf);
319 1056         53084 return $self->TableSpec_built_cnf;
320             }
321              
322             sub TableSpec_build_cnf {
323 66     66 0 15094 my $self = shift;
324 66 50       199 my %set_cnf = %{ $self->TableSpec_cnf || {} };
  66         1755  
325 66         3978 $self->TableSpec_built_cnf($self->default_TableSpec_cnf(\%set_cnf));
326             }
327              
328             sub default_TableSpec_cnf {
329 66     66 0 207 my $self = shift;
330 66   50     211 my $set = shift || {};
331              
332 66         143 my $data = $set;
333            
334            
335 66         653 my $table = &_table_name_safe($self->table);
336            
337 66         476 my $is_virtual = $self->_is_virtual_source;
338 66 50       1634 my $defs_i = $is_virtual ? 'ra-icon-pg-red' : 'ra-icon-pg';
339 66 50       208 my $defm_i = $is_virtual ? 'ra-icon-pg-multi-red' : 'ra-icon-pg-multi';
340            
341             # FIXME: These defaults cannot be seen via call from related tablespec, because of
342             # a circular logic situation. For base-defaults, see apply_TableSpec above
343             # This is one of the reasons the whole TableSpec design needs to be refactored
344 66         159 my %defaults = ();
345 66 50 33     283 $defaults{iconCls} = $data->{singleIconCls} if ($data->{singleIconCls} and ! $data->{iconCls});
346 66   66     521 $defaults{iconCls} = $defaults{iconCls} || $data->{iconCls} || $defs_i;
347 66   66     269 $defaults{multiIconCls} = $data->{multiIconCls} || $defm_i;
348 66   33     399 $defaults{singleIconCls} = $data->{singleIconCls} || $defaults{iconCls};
349 66   33     309 $defaults{title} = $data->{title} || $table;
350 66   33     355 $defaults{title_multi} = $data->{title_multi} || $defaults{title};
351 66         2248 ($defaults{display_column}) = $self->primary_columns;
352            
353 66 50       3591 my @display_columns = $data->{display_column} ? ( $data->{display_column} ) : $self->primary_columns;
354              
355             # row_display coderef overrides display_column to provide finer grained display control
356             my $orig_row_display = $data->{row_display} || sub {
357 0     0   0 my $record = $_;
358 0         0 my $title = join('/',map { $record->{$_} || '' } @display_columns);
  0         0  
359 0         0 $title = sprintf('%.13s',$title) . '...' if (length $title > 13);
360 0         0 return $title;
361 66   50     634 };
362            
363             $defaults{row_display} = sub {
364 0     0   0 my $display = $orig_row_display->(@_);
365 0 0       0 return $display if (ref $display);
366             return {
367             title => $display,
368             iconCls => $defaults{singleIconCls}
369 0         0 };
370 66         452 };
371            
372 66         191 my $rel_trans = {};
373            
374 66         243 $defaults{related_column_property_transforms} = $rel_trans;
375            
376            
377             #my $defs = \%defaults;
378             #my $col_cnf = $self->default_TableSpec_cnf_columns($set);
379             #$defs = merge($defs,$col_cnf);
380             #return merge($defs, $set);
381              
382 66         1287 %defaults = ( %defaults, %$set );
383 66         272 my $defs = \%defaults;
384 66         561 my $col_cnf = $self->default_TableSpec_cnf_columns($defs);
385 66         191 $defs->{columns} = $col_cnf->{columns};
386            
387 66         1701 return $defs;
388             }
389              
390             sub _is_virtual_source {
391 176     176   418 my $self = shift;
392             return (
393 176   33     3655 $self->result_source_instance->can('is_virtual') &&
394             $self->result_source_instance->is_virtual
395             );
396             }
397              
398             sub default_TableSpec_cnf_columns {
399 66     66 0 186 my $self = shift;
400 66   50     308 my $set = shift || {};
401              
402 66         206 my $data = $set;
403            
404 66         483 my @col_order = $self->default_TableSpec_cnf_column_order($set);
405            
406 66         207 my $cols = { map { $_ => {} } @col_order };
  443         1241  
407            
408             # lowest precidence:
409             #$cols = merge($cols,$set->{column_properties_defaults} || {});
410 66 50       327 %$cols = ( %$cols, %{ $set->{column_properties_defaults} || {}} );
  66         755  
411              
412             #$cols = merge($cols,$set->{column_properties_ordered} || {});
413 66 50       292 %$cols = ( %$cols, %{ $set->{column_properties_ordered} || {}} );
  66         427  
414            
415             # higher precidence:
416             #$cols = merge($cols,$set->{column_properties} || {});
417 66 50       251 %$cols = ( %$cols, %{ $set->{column_properties} || {}} );
  66         389  
418            
419 66         1931 my $data_types = $self->TableSpec_data_type_profiles;
420             #scream(keys %$cols);
421            
422 66         3264 my $is_virtual = $self->_is_virtual_source;
423            
424 66         1432 foreach my $col (keys %$cols) {
425            
426 443 100       1918 my $is_phy = $self->has_column($col) ? 1 : 0;
427 443         40239 $cols->{$col}{is_phy_colname} = $is_phy; #<-- track if this is also a physical column name
428              
429 443         804 my $is_local = $is_phy;
430            
431             # If this is both a local column and a relationship, allow the rel to take over
432             # if 'priority_rel_columns' is true:
433             $is_local = 0 if (
434             $is_local and
435             $self->has_relationship($col) and
436 443 50 100     7541 $set->{'priority_rel_columns'}
      66        
437             );
438            
439             # -- If priority_rel_columns is on but we need to exclude a specific column:
440             $is_local = 1 if (
441             ! $is_local and
442             $set->{no_priority_rel_column} and
443 443 0 66     18382 $set->{no_priority_rel_column}->{$col} and
      33        
      0        
444             $is_phy
445             );
446             # --
447            
448             # Never allow a rel col to take over a primary key:
449 443         7439 my %pri_cols = map {$_=>1} $self->primary_columns;
  456         17822  
450 443 100       1450 $is_local = 1 if ($pri_cols{$col});
451            
452 443 100       1124 unless ($is_local) {
453             # is it a rel col ?
454 114 50       2247 if($self->has_relationship($col)) {
455 114         6621 my $info = $self->relationship_info($col);
456            
457 114         4294 $cols->{$col}->{relationship_info} = $info;
458 114         571 my $cond_data = $self->parse_relationship_cond($info->{cond});
459 114         1035 $cols->{$col}->{relationship_cond_data} = { %$cond_data, %$info };
460            
461 114 100 100     994 if ($info->{attrs}->{accessor} eq 'single' || $info->{attrs}->{accessor} eq 'filter') {
    50          
462            
463             # -- NEW: Virtual Single Relationship - will be read-only
464 49 50 33     277 unless($cond_data->{foreign} && $cond_data->{self}) {
465 0         0 $cols->{$col}{virtualized_single_rel} = 1;
466 0         0 $cols->{$col}{allow_add} = 0;
467 0         0 $cols->{$col}{allow_edit} = 0;
468 0         0 next;
469             }
470             # --
471            
472             # New: pass the is_nullable flag in from the local FK column:
473 49 50       228 if($self->has_column($cond_data->{self})) {
474             $cols->{$col}{is_nullable} = $self->column_info($cond_data->{self})
475 49 100       4231 ->{is_nullable} ? 1 : 0;
476             }
477            
478             # Use TableSpec_related_get_set_conf instead of TableSpec_related_get_conf
479             # to prevent possible deep recursion:
480            
481 49         5018 my $display_column = $self->TableSpec_related_get_set_conf($col,'display_column');
482 49         168 my $display_columns = $self->TableSpec_related_get_set_conf($col,'display_columns');
483            
484             # -- auto_editor_params/auto_editor_type can be defined in either the local column
485             # properties, or the remote TableSpec conf
486 49   100     172 my $auto_editor_type = $self->TableSpec_related_get_set_conf($col,'auto_editor_type') || 'combo';
487 49   100     156 my $auto_editor_params = $self->TableSpec_related_get_set_conf($col,'auto_editor_params') || {};
488 49   50     173 my $auto_editor_win_params = $self->TableSpec_related_get_set_conf($col,'auto_editor_win_params') || {};
489 49   66     284 $cols->{$col}->{auto_editor_type} = $cols->{$col}->{auto_editor_type} || $auto_editor_type;
490 49   100     253 $cols->{$col}->{auto_editor_params} = $cols->{$col}->{auto_editor_params} || {};
491             $cols->{$col}->{auto_editor_params} = {
492             %$auto_editor_params,
493 49         224 %{$cols->{$col}->{auto_editor_params}}
  49         249  
494             };
495             # --
496            
497 49 0 33     224 $display_column = $display_columns->[0] if (
      33        
498             ! defined $display_column and
499             ref($display_columns) eq 'ARRAY' and
500             @$display_columns > 0
501             );
502            
503             ## fall-back set the display_column to the first key
504 49 50       118 ($display_column) = $self->primary_columns unless ($display_column);
505            
506 49 50 33     302 $display_columns = [ $display_column ] if (
507             ! defined $display_columns and
508             defined $display_column
509             );
510            
511 49 50       137 die "$col doesn't have display_column or display_columns set!" unless ($display_column);
512            
513 49         152 $cols->{$col}->{displayField} = $display_column;
514 49         153 $cols->{$col}->{display_columns} = $display_columns; #<-- in progress - used for grid instead of combo
515            
516             #TODO: needs to be more generalized/abstracted
517             #open_url, if defined, will add an autoLoad link to the renderer to
518             #open/navigate to the related item
519 49         147 $cols->{$col}->{open_url} = $self->TableSpec_related_get_set_conf($col,'open_url');
520            
521             $cols->{$col}->{valueField} = $cond_data->{foreign}
522 49 50       189 or die "couldn't get foreign col condition data for $col relationship!";
523            
524             $cols->{$col}->{keyField} = $cond_data->{self}
525 49 50       196 or die "couldn't get self col condition data for $col relationship!";
526            
527 49         249 next;
528             }
529             elsif($info->{attrs}->{accessor} eq 'multi') {
530 65         394 $cols->{$col}->{title_multi} = $self->TableSpec_related_get_set_conf($col,'title_multi');
531 65         245 $cols->{$col}->{multiIconCls} = $self->TableSpec_related_get_set_conf($col,'multiIconCls');
532 65         251 $cols->{$col}->{open_url_multi} = $self->TableSpec_related_get_set_conf($col,'open_url_multi');
533            
534             $cols->{$col}->{open_url_multi_rs_join_name} =
535 65   50     272 $self->TableSpec_related_get_set_conf($col,'open_url_multi_rs_join_name') || 'me';
536             }
537            
538             # New: add the 'relcol' profile to relationship columns:
539 65   100     434 $cols->{$col}->{profiles} ||= [];
540 65         163 push @{$cols->{$col}->{profiles}}, 'relcol';
  65         283  
541 65 50       190 push @{$cols->{$col}->{profiles}}, 'virtual_source' if ($is_virtual);
  0         0  
542 65 50       283 push @{$cols->{$col}->{profiles}}, 'multirel' if ($info->{attrs}->{accessor} eq 'multi');
  65         303  
543             }
544 65         231 next;
545             }
546            
547             ## WARNING! This logic overlaps with logic further up (in create_result_TableSpec) FIXME!
548 329         1454 my $info = $self->column_info($col);
549 329         32271 my @profiles = ();
550            
551 329 100       1222 push @profiles, $info->{is_nullable} ? 'nullable' : 'notnull';
552 329 100       927 push @profiles, 'autoinc' if ($info->{is_auto_increment});
553            
554 329   50     1154 my $type_profile = $data_types->{$info->{data_type}} || ['text'];
555            
556             # -- PostgreSQL override until array columns are supported (Github Issue #55):
557             $type_profile = ['unsearchable','virtual_source'] if (
558 329 50       968 $info->{data_type} =~ /\[/ #<-- if the data_type contains a square backect, i.e. 'text[]'
559             );
560             # --
561            
562 329 50       869 $type_profile = [ $type_profile ] unless (ref $type_profile);
563 329         773 push @profiles, @$type_profile;
564            
565             $cols->{$col}->{profiles} = [ $cols->{$col}->{profiles} ] if (
566             defined $cols->{$col}->{profiles} and
567             not ref $cols->{$col}->{profiles}
568 329 50 66     1237 );
569 329 100       810 push @profiles, @{$cols->{$col}->{profiles}} if ($cols->{$col}->{profiles});
  79         233  
570            
571 329 50       730 push @profiles, 'virtual_source' if ($is_virtual);
572            
573 329         745 $cols->{$col}->{profiles} = \@profiles;
574            
575             ## --
576 329         593 my $editor = {};
577            
578             ## Set the 'default' field value to match the default from the db (if exists) for this column:
579 329 100       774 $editor->{value} = $info->{default_value} if (exists $info->{default_value});
580            
581             # -- NEW:
582             # ScalarRef values mean literal SQL which should be evaluated at the time. New feature in
583             # RapidApp::JSON::MixedEncoder supports CodeRef values, which call them at encode time. This
584             # lets us set the default editor value to what it should be at the time the form is loaded.
585 329 50 50     1428 if((ref($info->{default_value})||'') eq 'SCALAR') {
586             $editor->{value} = sub {
587 0     0   0 my $value = $info->{default_value};
588             try {
589             # Actually ask the database via calling a select on the literal SQL. We're in a try
590             # block so if any of this fails, we fall back to the original ScalarRef which will
591             # probably end up being undef
592             $value = RapidApp->active_request_context
593 0         0 ->stash->{'RAPIDAPP_DISPATCH_MODULE'} # only way to get Module by the time we're called in the view
594             ->ResultSource->schema->storage->dbh
595             ->selectrow_arrayref( "SELECT $$value" )->[0];
596 0         0 };
597 0         0 return $value;
598             } unless (
599             # just because this one is so common, don't waste resources asking the database
600 0 0       0 ${$info->{default_value}} eq 'null'
  0         0  
601             );
602             }
603             # --
604            
605            
606             ## This sets additional properties of the editor for numeric type columns according
607             ## to the DBIC schema (max-length, signed/unsigned, float vs int). The API with "profiles"
608             ## didn't anticipate this fine-grained need, so 'extra_properties' was added specifically
609             ## to accomidate this (see special logic in TableSpec::Column):
610             ## note: these properties only apply if the editor xtype is 'numberfield' which we assume,
611             ## and is already set from the profiles of 'decimal', 'float', etc
612 329 100 66     1011 my $unsigned = ($info->{extra} && $info->{extra}->{unsigned}) ? 1 : 0;
613 329 100       786 $editor->{allowNegative} = \0 if ($unsigned);
614            
615 329 100       767 if($info->{size}) {
616 216         480 my $size = $info->{size};
617            
618             # Special case for 'float'/'decimal' with a specified precision (where 0 is the same as int):
619 216 100       535 if(ref $size eq 'ARRAY' ) {
620 12         52 my ($s,$p) = @$size;
621 12         35 $size = $s;
622 12         58 $editor->{maxValue} = ('9' x $s);
623 12 50       46 $size += 1 unless ($unsigned); #<-- room for a '-'
624 12 50 33     76 if ($p && $p > 0) {
625 12         61 $editor->{maxValue} .= '.' . ('9' x $p);
626 12         32 $size += $p + 1 ; #<-- precision plus a spot for '.' in the max field length
627 12         37 $editor->{decimalPrecision} = $p;
628             }
629             else {
630 0         0 $editor->{allowDecimals} = \0;
631             }
632             }
633 216         530 $editor->{maxLength} = $size;
634             }
635            
636 329 100       1010 if(keys %$editor > 0) {
637 228   100     919 $cols->{$col}->{extra_properties} = $cols->{$col}->{extra_properties} || {};
638             $cols->{$col}->{extra_properties} = merge($cols->{$col}->{extra_properties},{
639 228         1148 editor => $editor
640             });
641             }
642             ## --
643            
644             # --vv-- NEW: handling for 'enum' columns (Github Issue #30):
645 329 0 33     1423 if($info->{data_type} eq 'enum' && $info->{extra} && $info->{extra}{list}) {
      0        
646 0         0 my $list = $info->{extra}{list};
647            
648 0         0 my $selections = [];
649             # Null choice:
650             push @$selections, {
651             # #A9A9A9 = light grey
652             text => '<span style="color:#A9A9A9;">(None)</span>', value => undef
653 0 0       0 } if ($info->{is_nullable});
654            
655             push @$selections, map {
656 0         0 { text => $_, value => $_ }
  0         0  
657             } @$list;
658            
659             $cols->{$col}{menu_select_editor} = {
660             #mode: 'combo', 'menu' or 'cycle':
661 0         0 mode => 'menu',
662             selections => $selections
663             };
664              
665             # New: also save the list of possible values in a hashref...
666             # This is being done so that they can be pre-validated in
667             # quick search, needed for Postfix (Github Issue #56)
668             # TODO: not happy about having to do this - revisit later
669 0         0 $cols->{$col}{enum_value_hash} = { map {$_=>1} @$list }
  0         0  
670             }
671             # --^^--
672            
673             }
674              
675 66         375 return { columns => $cols };
676             }
677              
678             sub TableSpec_valid_db_columns {
679 173     173 0 390 my $self = shift;
680              
681 173         368 my @single_rels = ();
682 173         326 my @multi_rels = ();
683 173         399 my @virtual_single_rels = ();
684              
685 173         334 my %fk_cols = ();
686 173         3494 my %pri_cols = map {$_=>1} $self->primary_columns;
  190         7498  
687              
688 173         4656 foreach my $rel ($self->relationships) {
689 343         16131 my $info = $self->relationship_info($rel);
690            
691 343         13701 my $accessor = $info->{attrs}->{accessor};
692            
693             # 'filter' means single, but the name is also a local column
694             $accessor = 'single' if (
695             $accessor eq 'filter' and
696             $self->TableSpec_cnf->{'priority_rel_columns'} and
697             !(
698             $self->TableSpec_cnf->{'no_priority_rel_column'} and
699             $self->TableSpec_cnf->{'no_priority_rel_column'}->{$rel}
700             ) and
701 343 100 100     3625 ! $pri_cols{$rel} #<-- exclude primary column names. TODO: this check is performed later, fix
      33        
      66        
      100        
702             );
703            
704 343 100       7244 if($accessor eq 'single') {
    100          
705 99         570 my $cond_info = $self->parse_relationship_cond($info->{cond});
706 99 50 33     496 if($cond_info->{self} && $cond_info->{foreign}) {
707 99         260 push @single_rels, $rel;
708 99         195 my ($fk) = keys %{$info->{attrs}->{fk_columns}};
  99         407  
709 99 100       385 $fk_cols{$fk} = $rel if($fk);
710             }
711             else {
712             # (Github Issue #40)
713             # New: "virtual" single rels are relationships for which we
714             # cannot introspect in both directions (i.e. not physical
715             # foreign keys). These are still "single" in that they map to
716             # one related row, but will not be editable and not have a
717             # open link (yet)
718 0         0 push @virtual_single_rels, $rel;
719             }
720             }
721             elsif($accessor eq 'multi') {
722 175         517 push @multi_rels, $rel;
723             }
724             }
725              
726 173         1125 $self->TableSpec_set_conf('relationship_column_names',\@single_rels);
727 173         3806 $self->TableSpec_set_conf('multi_relationship_column_names',\@multi_rels);
728 173         3425 $self->TableSpec_set_conf('relationship_column_fks_map',\%fk_cols);
729              
730             # New: move single rels up to immediately follow their FK column:
731 173 100       3709 my @cols = map { $_, ( $fk_cols{$_} ? $fk_cols{$_} : () ) } $self->columns;
  996         4919  
732              
733 173         839 return uniq(@cols,@single_rels,@multi_rels,@virtual_single_rels);
734             }
735              
736             # There is no longer extra logic at this stage because we're
737             # backing off of the entire original "ordering" design:
738 66     66 0 476 sub default_TableSpec_cnf_column_order { (shift)->TableSpec_valid_db_columns }
739              
740             # Tmp code: these are all key names that may be used to set column
741             # properties (column TableSpecs). We are keeping track of them to
742             # use to for remapping while the TableSpec_cnf refactor/consolidation
743             # is underway...
744             my @col_prop_names = qw(
745             columns
746             column_properties
747             column_properties_ordered
748             column_properties_defaults
749             );
750             my %col_prop_names = map {$_=>1} @col_prop_names;
751              
752             # The TableSpec_set_conf method is overly complex to allow
753             # flexible arguments as either hash or hashref, and because of
754             # the special case of setting the nested 'column_properties'
755             # param, if specified as the first argument, and then be able to
756             # accept its sub params as either a hash or a hashref. In hindsight,
757             # allowing this was probably not worth the extra maintenace/code and
758             # was too fancy for its own good (since this case may or may not
759             # shift the key/value positions in the arg list) but it is a part
760             # of the API for now...
761             sub TableSpec_set_conf {
762 824     824 0 1393 my $self = shift;
763 824 50       1967 die "TableSpec_set_conf(): bad arguments" unless (scalar(@_) > 0);
764            
765             # First arg can be a hashref - deref and call again:
766 824 50       1702 if(ref($_[0])) {
767 0 0 0     0 die "TableSpec_set_conf(): bad arguments" unless (
768             ref($_[0]) eq 'HASH' and
769             scalar(@_) == 1
770             );
771 0         0 return $self->TableSpec_set_conf(%{$_[0]})
  0         0  
772             }
773            
774 824         15871 $self->TableSpec_built_cnf(undef); #<-- FIXME!!
775            
776             # Special handling for setting 'column_properties':
777 824 100       12205 if ($col_prop_names{$_[0]}) {
778 50         102 shift @_; #<-- pull out the 'column_properties' first arg
779 50         171 return $self->_TableSpec_set_column_properties(@_);
780             };
781            
782             # Enforce even number of args for good measure:
783 774 50       1859 die join(' ',
784             'TableSpec_set_conf( %cnf ):',
785             "odd number of args in key/value list:", Dumper(\@_)
786             ) if (scalar(@_) & 1);
787            
788 774         2155 my %cnf = @_;
789            
790 774         2036 for my $param (keys %cnf) {
791             # Also make sure all the keys (even positions) are simple scalars:
792 1186 50       10157 die join(' ',
793             'TableSpec_set_conf( %cnf ):',
794             'found ref in key position:', Dumper($_)
795             ) if (ref($param));
796            
797 1186 100       2396 if($col_prop_names{$param}) {
798             # Also handle column_properties specified with other params:
799             die join(' ',
800             'TableSpec_set_conf( %cnf ): Expected',
801             "HashRef value for config key '$param':", Dumper($cnf{$param})
802 57 50       205 ) unless (ref($cnf{$param}) eq 'HASH');
803 57         374 $self->_TableSpec_set_column_properties($cnf{$param});
804             }
805             else {
806 1129         19610 $self->TableSpec_cnf->{$param} = $cnf{$param}
807             }
808             }
809             }
810              
811             # Special new internal method for setting column properties and
812             # properly handle backward compatability. Simultaneously sets/updates
813             # the cnf key names for all the 'column_properties' names that are
814             # currently supported by the API (as references pointing to the same
815             # single config HashRef). This is only temporary and is a throwback
816             # caused by the older/original API design for the TableSpec_cnf and
817             # will be removed later on once the other config names can be depricated
818             # along with other planned refactored. This is just a stop-gap to
819             # allow this refactor to be done in stages...
820             sub _TableSpec_set_column_properties {
821 165     165   293 my $self = shift;
822 165 50       387 die "TableSpec_set_conf( column_properties => %cnf ): bad args"
823             unless (scalar(@_) > 0);
824            
825             # First arg can be a hashref - deref and call again:
826 165 100       368 if(ref($_[0])) {
827 58 50 33     336 die "TableSpec_set_conf( column_properties => %cnf ): bad args" unless (
828             ref($_[0]) eq 'HASH' and
829             scalar(@_) == 1
830             );
831 58         116 return $self->_TableSpec_set_column_properties(%{$_[0]})
  58         306  
832             }
833            
834             # Enforce even number of args for good measure:
835 107 50       244 die join(' ',
836             'TableSpec_set_conf( column_properties => %cnf ):',
837             "odd number of args in key/value list:", Dumper(\@_)
838             ) if (scalar(@_) & 1);
839            
840 107         389 my %cnf = @_;
841            
842             # Also make sure all the keys (even positions) are simple scalars:
843             ref($_) and die join(' ',
844             'TableSpec_set_conf( column_properties => %cnf ):',
845             'found ref in key position:', Dumper($_)
846 107   50     787 ) for (keys %cnf);
847            
848 107         495 my %valid_colnames = map {$_=>1} ($self->TableSpec_valid_db_columns);
  744         1391  
849            
850 107         243 my $col_props;
851 107   100     2355 $col_props ||= $self->TableSpec_cnf->{$_} for (@col_prop_names);
852 107   100     6713 $col_props ||= {};
853            
854 107         320 for my $col (keys %cnf) {
855             warn join(' ',
856             "Ignoring config for unknown column name '$col'",
857             "in $self TableSpec config\n"
858 681 50 0     1485 ) and next unless ($valid_colnames{$col});
859 681         1106 $col_props->{$col} = $cnf{$col};
860             }
861            
862 107         1954 $self->TableSpec_cnf->{$_} = $col_props for (@col_prop_names);
863             }
864              
865              
866             # New function for updating/merging in column configs. This allows
867             # setting certain column configs without overwriting existing config
868             # keys that are not being specified:
869             sub TableSpec_merge_columns_conf {
870 0     0 0 0 my $self = shift;
871 0         0 my $conf = shift;
872            
873 0 0       0 die "TableSpec_merge_columns_conf( \%columns ): bad args"
874             unless (ref($conf) eq 'HASH');
875            
876 0   0     0 my $existing = $self->TableSpec_get_conf('columns') || {};
877            
878 0         0 my @cols = uniq( keys %$conf, keys %$existing );
879            
880             my %new = ( map {
881 0         0 $_ => {
882 0 0       0 %{ $existing->{$_} || {} },
883 0 0       0 %{ $conf->{$_} || {} },
  0         0  
884             }
885             } @cols );
886            
887 0         0 return $self->TableSpec_set_conf( columns => \%new );
888             }
889              
890              
891              
892             sub TableSpec_get_conf {
893 1654     1654 0 16742 my $self = shift;
894 1654   50     4145 my $param = shift || return undef;
895 1654   66     5413 my $storage = shift || $self->get_built_Cnf;
896            
897             # Special: map all column prop names into 'column_properties'
898 1654 100       23239 $param = 'column_properties' if ($col_prop_names{$param});
899            
900 1654         4106 my $value = $storage->{$param};
901            
902             # --- FIXME FIXME FIXME
903             # In the original design of the TableSpec_cnf internals, which
904             # was too fancy for its own good, meta/type information was
905             # transparently stored to be able to do things like remember
906             # the order of keys in hashes, auto dereference, etc. This has
907             # been unfactored and converted to simple key/values since, however,
908             # places that might still call TableSpec_get_conf still expect
909             # to get back lists instead of ArrayRefs/HashRefs in certain
910             # places. These places should be very limited (part of the reason
911             # it was decided this whole thing wasn't worth it, because it just
912             # wasn't used enough), but for now, to honor the original API (mostly)
913             # we're dereferencing according to wantarray, since all the places
914             # that expect to get lists back obviously call TableSpec_get_conf
915             # in LIST context. This should not be kept this way for too long,
916             # however! It is just temporary until those outside places
917             # can be confirmed and eliminated, or a proper deprecation plan
918             # can be made, should that even be needed...
919            
920 1654 50 66     4554 if(wantarray && ref($value)) {
921 0 0 0     0 cluck join("\n",'',
922             " WARNING: calling TableSpec_get_conf() in LIST context",
923             " is deprecated, please update your code.",
924             " --> Auto-dereferencing param '$param' $value",'',
925             '') if (ref($value) eq 'ARRAY' || ref($value) eq 'HASH');
926 0 0       0 return @$value if (ref($value) eq 'ARRAY');
927 0 0       0 return %$value if (ref($value) eq 'HASH');
928             }
929            
930             # When trying to get a param that does not exist, return an
931             # empty list if called in LIST context, otherwise undef
932 1654 50       6488 return wantarray ? () : undef unless (exists $storage->{$param});
    100          
933             # ---
934            
935 996         5880 return $value;
936             }
937              
938              
939             sub TableSpec_has_conf {
940 84     84 0 219 my $self = shift;
941 84         211 my $param = shift;
942 84   33     444 my $storage = shift || $self->get_built_Cnf;
943 84 50       2883 return 1 if (exists $storage->{$param});
944 0         0 return 0;
945             }
946              
947              
948             sub TableSpec_related_class {
949 554     554 0 934 my $self = shift;
950 554   50     1135 my $rel = shift || return undef;
951 554   50     10105 my $info = $self->relationship_info($rel) || return undef;
952 554         21992 my $relclass = $info->{class};
953            
954 554         28172 eval "require $relclass;";
955            
956             #my $relclass = $self->related_class($rel) || return undef;
957 554 50       5175 $relclass->can('TableSpec_get_conf') || return undef;
958 554         1813 return $relclass;
959             }
960              
961             # Gets a TableSpec conf param, if exists, from a related Result Class
962             sub TableSpec_related_get_conf {
963 0     0 0 0 my $self = shift;
964 0   0     0 my $rel = shift || return undef;
965 0   0     0 my $param = shift || return undef;
966            
967 0   0     0 my $relclass = $self->TableSpec_related_class($rel) || return undef;
968              
969 0         0 return $relclass->TableSpec_get_conf($param);
970             }
971              
972             # Gets a TableSpec conf param, if exists, from a related Result Class,
973             # but uses the already 'set' params in TableSpec_cnf as storage, so that
974             # get_built_cnf doesn't get called.
975             sub TableSpec_related_get_set_conf {
976 554     554 0 993 my $self = shift;
977 554   50     1192 my $rel = shift || return undef;
978 554   50     1268 my $param = shift || return undef;
979            
980 554   50     1432 my $relclass = $self->TableSpec_related_class($rel) || return undef;
981              
982             #return $relclass->TableSpec_get_conf($param,$relclass->TableSpec_cnf);
983 554         1568 return $relclass->TableSpec_get_set_conf($param);
984             }
985              
986             # The "set conf" is different from the "built conf" in that it is passive, and only
987             # returns the values which have been expressly "set" on the Result class with a
988             # "TableSpec_set_conf" call. The built conf reaches out to code to build a configuration,
989             # which causes recursive limitations in that code that reaches out to other TableSpec
990             # classes.
991             sub TableSpec_get_set_conf {
992 858     858 0 1484 my $self = shift;
993 858   50     1839 my $param = shift || return undef;
994 858         16731 return $self->TableSpec_get_conf($param,$self->TableSpec_cnf);
995             }
996              
997              
998             # TODO: Find a better way to handle this. Is there a real API
999             # in DBIC to find this information?
1000             sub get_foreign_column_from_cond {
1001 0     0 0 0 my $self = shift;
1002 0         0 my $cond = shift;
1003            
1004 0 0 0     0 die "currently only single-key hashref conditions are supported" unless (
1005             ref($cond) eq 'HASH' and
1006             scalar keys %$cond == 1
1007             );
1008            
1009 0         0 foreach my $i (%$cond) {
1010 0         0 my ($side,$col) = split(/\./,$i);
1011 0 0 0     0 return $col if (defined $col and $side eq 'foreign');
1012             }
1013            
1014 0         0 die "Failed to find forein column from condition: " . Dumper($cond);
1015             }
1016              
1017             # This function parses 'foreign' and 'self' column names from the
1018             # 'cond' of a defined in a DBIC relationship into a hashref. It is
1019             # only able to do this for simple, single-key foreign key rels
1020             # of the form: { "foreign.id_col" => "self.fk_col" }
1021             # All other forms, such as multi-keys and CodeRefs, will return
1022             # and empty HashRef. The only reason we really need this information
1023             # outside of DBIC is for editable single rels (FKs) to be able
1024             # to present selection dialogs (i.e. dropdowns) and currently
1025             # the "open" magnify links, but the open links are planned to be
1026             # changed to reference URLs based on the relationship name, which
1027             # will remove this dependency and allow open links for any relationship
1028             # column, including even those with CodeRef conditions...
1029             sub parse_relationship_cond {
1030 507     507 0 1550 my ($self,$cond,$info) = @_;
1031            
1032             return {} unless (
1033 507 50 33     3246 ref($cond) eq 'HASH' and
1034             scalar keys %$cond == 1
1035             );
1036            
1037 507         1202 my $data = {};
1038 507         1571 foreach my $i (%$cond) {
1039 1014         3403 my ($side,$col) = split(/\./,$i);
1040 1014         3020 $data->{$side} = $col;
1041             }
1042 507         1490 return $data;
1043             }
1044              
1045             # Works like an around method modifier, but $self is expected as first arg and
1046             # $orig (method) is expected as second arg (reversed from a normal around modifier).
1047             # Calls the supplied method and returns what changed in the record from before to
1048             # after the call. e.g.:
1049             #
1050             # my ($changes) = $self->proxy_method_get_changed('update',{ foo => 'sdfds'});
1051             #
1052             # This is typically used for update, but could be any other method, too.
1053             #
1054             # Detects/propogates wantarray context. Call like this to chain from another modifier:
1055             #my ($changes,@ret) = wantarray ?
1056             # $self->proxy_method_get_changed($orig,@_) :
1057             # @{$self->proxy_method_get_changed($orig,@_)};
1058             #
1059             sub proxy_method_get_changed {
1060 0     0 0 0 my $self = shift;
1061 0         0 my $method = shift;
1062            
1063 5     5   22746 no warnings 'uninitialized'; # because we might compare undef values
  5         13  
  5         4691  
1064            
1065 0         0 my $origRow = $self;
1066 0         0 my %old = ();
1067 0 0       0 if($self->in_storage) {
1068 0   0     0 $origRow = $self->get_from_storage || $self;
1069 0         0 %old = $origRow->get_columns;
1070             }
1071            
1072 0         0 my @ret = ();
1073             wantarray ?
1074 0 0       0 @ret = $self->$method(@_) :
1075             $ret[0] = $self->$method(@_);
1076            
1077 0         0 my %new = ();
1078 0 0       0 if($self->in_storage) {
1079 0         0 %new = $self->get_columns;
1080             }
1081            
1082             # This logic is duplicated in DbicLink2. Not sure how to avoid it, though,
1083             # and keep a clean API
1084 0         0 my @changed = ();
1085 0         0 foreach my $col (uniq(keys %new,keys %old)) {
1086 0 0 0     0 next if (! defined $new{$col} and ! defined $old{$col});
1087 0 0       0 next if ($new{$col} eq $old{$col});
1088 0         0 push @changed, $col;
1089             }
1090            
1091 0         0 my @new_changed = ();
1092 0         0 my $fk_map = $self->TableSpec_get_conf('relationship_column_fks_map');
1093 0         0 foreach my $col (@changed) {
1094 0 0       0 unless($fk_map->{$col}) {
1095 0         0 push @new_changed, $col;
1096 0         0 next;
1097             }
1098            
1099 0         0 my $rel = $fk_map->{$col};
1100 0         0 my $display_col = $self->TableSpec_related_get_set_conf($rel,'display_column');
1101            
1102 0         0 my $relOld = $origRow->$rel;
1103 0         0 my $relNew = $self->$rel;
1104            
1105 0 0 0     0 unless($display_col and ($relOld or $relNew)) {
      0        
1106 0         0 push @new_changed, $col;
1107 0         0 next;
1108             }
1109            
1110 0         0 push @new_changed, $rel;
1111            
1112 0 0 0     0 $old{$rel} = $relOld->get_column($display_col) if (exists $old{$col} and $relOld);
1113 0 0 0     0 $new{$rel} = $relNew->get_column($display_col) if (exists $new{$col} and $relNew);
1114             }
1115            
1116 0         0 @changed = @new_changed;
1117            
1118 0         0 my $col_props = $self->TableSpec_get_conf('columns');
1119            
1120             my %diff = map {
1121 0         0 $_ => {
1122             old => $old{$_},
1123             new => $new{$_},
1124             header => ($col_props->{$_} && $col_props->{$_}->{header}) ?
1125 0 0 0     0 $col_props->{$_}->{header} : $_
1126             }
1127             } @changed;
1128            
1129 0 0       0 return wantarray ? (\%diff,@ret) : [\%diff,@ret];
1130             }
1131              
1132              
1133             sub getOpenUrl {
1134 0     0 0 0 my $self = shift;
1135 0         0 return $self->TableSpec_get_conf('open_url');
1136             }
1137              
1138             sub getRestKey {
1139 183     183 0 11295 my $self = shift;
1140 183         782 my $rest_key_col = $self->TableSpec_get_conf('rest_key_column');
1141 183 50 33     803 return $rest_key_col if ($rest_key_col && $rest_key_col ne '');
1142 183         3968 my @pri = $self->primary_columns;
1143 183 50 33     10252 return $pri[0] if ($pri[0] && scalar @pri == 1);
1144 0         0 return undef;
1145             }
1146              
1147             ### Util functions: to be called in Row-object context
1148             sub apply_row_methods {
1149 54     54 0 148 my $class = shift;
1150            
1151             my %RowMethods = (
1152            
1153 0     0   0 getOpenUrl => sub { $class->TableSpec_get_conf('open_url') },
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1154            
1155             getRecordPkValue => sub {
1156 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1157 0         0 my @pk_vals = map { $self->get_column($_) } $self->primary_columns;
  0         0  
1158 0         0 return join('~$~',@pk_vals);
1159             },
1160            
1161             getRestKeyVal => sub {
1162 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1163 0 0       0 my $col = $class->getRestKey or return $self->getRecordPkValue;
1164 0     0   0 return try{$self->get_column($col)};
  0         0  
1165             },
1166            
1167             getRestPath => sub {
1168 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1169 0 0       0 my $url = $class->getOpenUrl or return undef;
1170 0 0       0 my $val = $self->getRestKeyVal or return undef;
1171 0         0 return "$url/$val";
1172             },
1173            
1174             getDisplayValue => sub {
1175 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1176 0         0 my $display_column = $class->TableSpec_get_conf('display_column');
1177 0 0       0 return $self->get_column($display_column) if ($self->has_column($display_column));
1178 0         0 return $self->getRecordPkValue;
1179             },
1180            
1181             inlineNavLink => sub {
1182 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1183 0   0     0 my $text = shift || '<span>open</span>';
1184 0         0 my %attrs = ( class => "ra-nav-link ra-icon-magnify-tiny", @_ );
1185              
1186 0 0       0 my $title = $self->getDisplayValue or return undef;
1187 0 0       0 my $url = $self->getRestPath or return undef;
1188            
1189 0         0 %attrs = (
1190             href => '#!' . $url,
1191             title => $title,
1192             %attrs
1193             );
1194            
1195 0         0 my $attr_str = join(' ',map { $_ . '="' . $attrs{$_} . '"' } keys %attrs);
  0         0  
1196 0         0 return '<a ' . $attr_str . '>' . $text . '</a>';
1197             },
1198              
1199             displayWithLink => sub {
1200 0     0   0 my $self = shift;
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
        0      
1201 0         0 return $self->getDisplayValue . ' ' . $self->inlineNavLink;
1202             }
1203 54         1437 );
1204            
1205             # --- Actualize/load methods into the Row object namespace:
1206 54         261 foreach my $meth (keys %RowMethods) {
1207 5     5   45 no strict 'refs';
  5         15  
  5         1264  
1208 378         844 my $meth_name = join '::', $class, $meth;
1209 378         2717 *$meth_name = subname $meth_name => $RowMethods{$meth};
1210             }
1211             # ---
1212             }
1213              
1214              
1215             sub _table_name_safe {
1216 447     447   24139 my $arg = shift;
1217            
1218 447 50 33     5465 my $table = !(ref $arg) && $arg->can('table') ? $arg->table : $arg; # class method or straight function
1219              
1220 447 50 50     2524 $table = $$table if ((ref($table)||'') eq 'SCALAR'); # Handle ScalarRef values
1221 447         2232 $table = (reverse split(/\./,$table))[0]; # Handle 'db.table' and 'schema.db.table' formats
1222              
1223 447         1791 $table =~ s/[\'\"]//g; # Strip quotes
1224 447         1056 $table =~ s/\W/_/g; # Convert any non-word characters to underscore
1225              
1226 447         1464 $table
1227             }
1228              
1229              
1230              
1231             ### -- old, pre-rest inlineNavLink:
1232             ## This function creates links just like the JavaScript function Ext.ux.RapidApp.inlineLink
1233             #use URI::Escape;
1234             #sub inlineNavLink {
1235             # my $self = shift;
1236             # my $text = shift || '<span>open</span>';
1237             # my %attrs = ( class => "magnify-link-tiny", @_ );
1238             # my $loadCfg = delete $attrs{loadCfg} || {};
1239             #
1240             # my $title = $self->getDisplayValue || return undef;
1241             # my $url = $self->getOpenUrl || return undef;
1242             # my $pk_val = $self->getRecordPkValue || return undef;
1243             #
1244             # $loadCfg = merge({
1245             # title => $title,
1246             # autoLoad => {
1247             # url => $url,
1248             # params => { '___record_pk' => $pk_val }
1249             # }
1250             # },$loadCfg);
1251             #
1252             # my $href = '#loadcfg:data=' . uri_escape(encode_json($loadCfg));
1253             # my $onclick = 'return Ext.ux.RapidApp.InlineLinkHandler.apply(this,arguments);';
1254             #
1255             # %attrs = (
1256             # href => $href,
1257             # onclick => $onclick,
1258             # ondblclick => $onclick,
1259             # title => $title,
1260             # %attrs
1261             # );
1262             #
1263             # my $attr_str = join(' ',map { $_ . '="' . $attrs{$_} . '"' } keys %attrs);
1264             #
1265             # return '<a ' . $attr_str . '>' . $text . '</a>';
1266             #
1267             #}
1268             #
1269              
1270              
1271             1;