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   80150 use strict;
  5         14  
  5         136  
7 5     5   24 use warnings;
  5         8  
  5         138  
8              
9 5     5   405 use Sub::Name qw/subname/;
  5         471  
  5         266  
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   438 use RapidApp::Util qw(:all);
  5         11  
  5         2379  
16              
17 5     5   1724 use RapidApp::TableSpec;
  5         37  
  5         207  
18 5     5   2466 use RapidApp::Module::DbicCombo;
  5         18  
  5         337  
19 5     5   34 use Module::Runtime;
  5         11  
  5         42  
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         8 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       304 $self->has_column($m2m) and die "'$m2m' is already defined as a column.";
80 2 50       218 $self->has_relationship($m2m) and die "'$m2m' is already defined as a relationship.";
81              
82 2 50       546 my $rinfo = $self->relationship_info($local_rel) or die "'$local_rel' relationship not found";
83 2         184 eval('require ' . $rinfo->{class});
84            
85             die "m2m bridge relationship '$local_rel' is not a multi relationship"
86 2 50       18 unless ($rinfo->{attrs}->{accessor} eq 'multi');
87            
88 2         49 my $rrinfo = $rinfo->{class}->relationship_info($remote_rel);
89 2 50       91 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         12 Module::Runtime::require_module($rrinfo->{class});
97            
98 2         61 $rinfo->{table} = &_table_name_safe($rinfo->{class}->table);
99 2         19 $rrinfo->{table} = &_table_name_safe($rrinfo->{class}->table);
100            
101 2         18 $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         10 {%{$rinfo->{attrs}}, m2m_attrs => {
  2         46  
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       936 unless (exists $ENV{DBIC_OVERWRITE_HELPER_METHODS_OK});
142 2         27 $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 126 my $self = shift;
214             return (
215             defined $self->TableSpec_cnf and
216             defined $self->TableSpec_cnf->{apply_TableSpec_timestamp}
217 56   33     1312 );
218             }
219              
220             sub apply_TableSpec {
221 54     54 0 144 my $self = shift;
222 54 50       255 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         8816 $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       183 ) if ($opt{TableSpec_data_type_profiles});
235            
236 54         403 $self->TableSpec($self->create_result_TableSpec($self,%opt));
237            
238 54         2360 $self->TableSpec_rel_columns({});
239 54         1598 $self->TableSpec_cnf({});
240 54         1630 $self->TableSpec_built_cnf(undef);
241            
242 54         994 $self->apply_row_methods();
243            
244             # Just doing this to ensure we're initialized:
245 54         432 $self->TableSpec_set_conf( apply_TableSpec_timestamp => time );
246            
247             # --- Set some base defaults here:
248 54         1101 my $table = &_table_name_safe($self->table);
249 54         1848 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         878 columns => { map { $_ => {} } $self->columns }
  313         1358  
268             # --
269             );
270             # ---
271            
272 54         2429 return $self;
273             }
274              
275             sub create_result_TableSpec {
276 54     54 0 131 my $self = shift;
277 54         103 my $ResultClass = shift;
278 54 50       188 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
279            
280 54         855 my $table = &_table_name_safe($ResultClass->table);
281              
282 54         1524 my $TableSpec = RapidApp::TableSpec->new(
283             name => $table,
284             %opt
285             );
286              
287 54         1136 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         2642 foreach my $col ($ResultClass->columns) {
291 313         1880 my $info = $ResultClass->column_info($col);
292 313         29378 my @profiles = ();
293            
294 313 100       885 push @profiles, $info->{is_nullable} ? 'nullable' : 'notnull';
295 313 100       729 push @profiles, 'autoinc' if ($info->{is_auto_increment});
296            
297 313   50     866 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       791 $info->{data_type} =~ /\[/ #<-- if the data_type contains a square backect, i.e. 'text[]'
302             );
303             # --
304            
305 313 50       635 $type_profile = [ $type_profile ] unless (ref $type_profile);
306 313         595 push @profiles, @$type_profile;
307            
308 313         1231 $TableSpec->add_columns( { name => $col, profiles => \@profiles } );
309             }
310            
311 54         1506 return $TableSpec;
312             }
313              
314              
315             sub get_built_Cnf {
316 1056     1056 0 1883 my $self = shift;
317            
318 1056 100       18831 $self->TableSpec_build_cnf unless ($self->TableSpec_built_cnf);
319 1056         45215 return $self->TableSpec_built_cnf;
320             }
321              
322             sub TableSpec_build_cnf {
323 107     107 0 21397 my $self = shift;
324 107 50       220 my %set_cnf = %{ $self->TableSpec_cnf || {} };
  107         2291  
325 107         5836 $self->TableSpec_built_cnf($self->default_TableSpec_cnf(\%set_cnf));
326             }
327              
328             sub default_TableSpec_cnf {
329 107     107 0 266 my $self = shift;
330 107   50     350 my $set = shift || {};
331              
332 107         192 my $data = $set;
333            
334            
335 107         746 my $table = &_table_name_safe($self->table);
336            
337 107         599 my $is_virtual = $self->_is_virtual_source;
338 107 50       2201 my $defs_i = $is_virtual ? 'ra-icon-pg-red' : 'ra-icon-pg';
339 107 50       258 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 107         250 my %defaults = ();
345 107 50 33     411 $defaults{iconCls} = $data->{singleIconCls} if ($data->{singleIconCls} and ! $data->{iconCls});
346 107   66     596 $defaults{iconCls} = $defaults{iconCls} || $data->{iconCls} || $defs_i;
347 107   66     364 $defaults{multiIconCls} = $data->{multiIconCls} || $defm_i;
348 107   33     563 $defaults{singleIconCls} = $data->{singleIconCls} || $defaults{iconCls};
349 107   33     317 $defaults{title} = $data->{title} || $table;
350 107   33     351 $defaults{title_multi} = $data->{title_multi} || $defaults{title};
351 107         2773 ($defaults{display_column}) = $self->primary_columns;
352            
353 107 50       4755 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 107   50     926 };
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 107         552 };
371            
372 107         268 my $rel_trans = {};
373            
374 107         340 $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 107         1591 %defaults = ( %defaults, %$set );
383 107         352 my $defs = \%defaults;
384 107         700 my $col_cnf = $self->default_TableSpec_cnf_columns($defs);
385 107         240 $defs->{columns} = $col_cnf->{columns};
386            
387 107         2288 return $defs;
388             }
389              
390             sub _is_virtual_source {
391 258     258   593 my $self = shift;
392             return (
393 258   33     4583 $self->result_source_instance->can('is_virtual') &&
394             $self->result_source_instance->is_virtual
395             );
396             }
397              
398             sub default_TableSpec_cnf_columns {
399 107     107 0 246 my $self = shift;
400 107   50     338 my $set = shift || {};
401              
402 107         185 my $data = $set;
403            
404 107         592 my @col_order = $self->default_TableSpec_cnf_column_order($set);
405            
406 107         264 my $cols = { map { $_ => {} } @col_order };
  672         1221  
407            
408             # lowest precidence:
409             #$cols = merge($cols,$set->{column_properties_defaults} || {});
410 107 50       409 %$cols = ( %$cols, %{ $set->{column_properties_defaults} || {}} );
  107         1009  
411              
412             #$cols = merge($cols,$set->{column_properties_ordered} || {});
413 107 50       367 %$cols = ( %$cols, %{ $set->{column_properties_ordered} || {}} );
  107         593  
414            
415             # higher precidence:
416             #$cols = merge($cols,$set->{column_properties} || {});
417 107 50       308 %$cols = ( %$cols, %{ $set->{column_properties} || {}} );
  107         551  
418            
419 107         2459 my $data_types = $self->TableSpec_data_type_profiles;
420             #scream(keys %$cols);
421            
422 107         4653 my $is_virtual = $self->_is_virtual_source;
423            
424 107         2007 foreach my $col (keys %$cols) {
425            
426 672 100       2338 my $is_phy = $self->has_column($col) ? 1 : 0;
427 672         54592 $cols->{$col}{is_phy_colname} = $is_phy; #<-- track if this is also a physical column name
428              
429 672         1207 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 672 50 100     9587 $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 672 0 66     22395 $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 672         9663 my %pri_cols = map {$_=>1} $self->primary_columns;
  685         22859  
450 672 100       1974 $is_local = 1 if ($pri_cols{$col});
451            
452 672 100       1347 unless ($is_local) {
453             # is it a rel col ?
454 172 50       2865 if($self->has_relationship($col)) {
455 172         7751 my $info = $self->relationship_info($col);
456            
457 172         5301 $cols->{$col}->{relationship_info} = $info;
458 172         661 my $cond_data = $self->parse_relationship_cond($info->{cond});
459 172         1427 $cols->{$col}->{relationship_cond_data} = { %$cond_data, %$info };
460            
461 172 100 100     1213 if ($info->{attrs}->{accessor} eq 'single' || $info->{attrs}->{accessor} eq 'filter') {
    50          
462            
463             # -- NEW: Virtual Single Relationship - will be read-only
464 67 50 33     345 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 67 50       265 if($self->has_column($cond_data->{self})) {
474             $cols->{$col}{is_nullable} = $self->column_info($cond_data->{self})
475 67 100       5390 ->{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 67         5864 my $display_column = $self->TableSpec_related_get_set_conf($col,'display_column');
482 67         211 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 67   100     212 my $auto_editor_type = $self->TableSpec_related_get_set_conf($col,'auto_editor_type') || 'combo';
487 67   100     201 my $auto_editor_params = $self->TableSpec_related_get_set_conf($col,'auto_editor_params') || {};
488 67   50     184 my $auto_editor_win_params = $self->TableSpec_related_get_set_conf($col,'auto_editor_win_params') || {};
489 67   66     27048 $cols->{$col}->{auto_editor_type} = $cols->{$col}->{auto_editor_type} || $auto_editor_type;
490 67   100     294 $cols->{$col}->{auto_editor_params} = $cols->{$col}->{auto_editor_params} || {};
491             $cols->{$col}->{auto_editor_params} = {
492             %$auto_editor_params,
493 67         255 %{$cols->{$col}->{auto_editor_params}}
  67         324  
494             };
495             # --
496            
497 67 0 33     290 $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 67 50       159 ($display_column) = $self->primary_columns unless ($display_column);
505            
506 67 50 33     309 $display_columns = [ $display_column ] if (
507             ! defined $display_columns and
508             defined $display_column
509             );
510            
511 67 50       157 die "$col doesn't have display_column or display_columns set!" unless ($display_column);
512            
513 67         201 $cols->{$col}->{displayField} = $display_column;
514 67         169 $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 67         205 $cols->{$col}->{open_url} = $self->TableSpec_related_get_set_conf($col,'open_url');
520            
521             $cols->{$col}->{valueField} = $cond_data->{foreign}
522 67 50       232 or die "couldn't get foreign col condition data for $col relationship!";
523            
524             $cols->{$col}->{keyField} = $cond_data->{self}
525 67 50       217 or die "couldn't get self col condition data for $col relationship!";
526            
527 67         309 next;
528             }
529             elsif($info->{attrs}->{accessor} eq 'multi') {
530 105         534 $cols->{$col}->{title_multi} = $self->TableSpec_related_get_set_conf($col,'title_multi');
531 105         364 $cols->{$col}->{multiIconCls} = $self->TableSpec_related_get_set_conf($col,'multiIconCls');
532 105         376 $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 105   50     342 $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 105   100     499 $cols->{$col}->{profiles} ||= [];
540 105         233 push @{$cols->{$col}->{profiles}}, 'relcol';
  105         391  
541 105 50       285 push @{$cols->{$col}->{profiles}}, 'virtual_source' if ($is_virtual);
  0         0  
542 105 50       378 push @{$cols->{$col}->{profiles}}, 'multirel' if ($info->{attrs}->{accessor} eq 'multi');
  105         413  
543             }
544 105         307 next;
545             }
546            
547             ## WARNING! This logic overlaps with logic further up (in create_result_TableSpec) FIXME!
548 500         1797 my $info = $self->column_info($col);
549 500         41922 my @profiles = ();
550            
551 500 100       1602 push @profiles, $info->{is_nullable} ? 'nullable' : 'notnull';
552 500 100       1228 push @profiles, 'autoinc' if ($info->{is_auto_increment});
553            
554 500   50     1552 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 500 50       1275 $info->{data_type} =~ /\[/ #<-- if the data_type contains a square backect, i.e. 'text[]'
559             );
560             # --
561            
562 500 50       1125 $type_profile = [ $type_profile ] unless (ref $type_profile);
563 500         1025 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 500 50 66     1922 );
569 500 100       1077 push @profiles, @{$cols->{$col}->{profiles}} if ($cols->{$col}->{profiles});
  250         833  
570            
571 500 50       1020 push @profiles, 'virtual_source' if ($is_virtual);
572            
573 500         965 $cols->{$col}->{profiles} = \@profiles;
574            
575             ## --
576 500         814 my $editor = {};
577            
578             ## Set the 'default' field value to match the default from the db (if exists) for this column:
579 500 100       1002 $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 500 50 50     1795 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 500 100 66     9755 my $unsigned = ($info->{extra} && $info->{extra}->{unsigned}) ? 1 : 0;
613 500 100       936 $editor->{allowNegative} = \0 if ($unsigned);
614            
615 500 100       1095 if($info->{size}) {
616 336         554 my $size = $info->{size};
617            
618             # Special case for 'float'/'decimal' with a specified precision (where 0 is the same as int):
619 336 100       738 if(ref $size eq 'ARRAY' ) {
620 12         47 my ($s,$p) = @$size;
621 12         29 $size = $s;
622 12         56 $editor->{maxValue} = ('9' x $s);
623 12 50       50 $size += 1 unless ($unsigned); #<-- room for a '-'
624 12 50 33     68 if ($p && $p > 0) {
625 12         49 $editor->{maxValue} .= '.' . ('9' x $p);
626 12         31 $size += $p + 1 ; #<-- precision plus a spot for '.' in the max field length
627 12         35 $editor->{decimalPrecision} = $p;
628             }
629             else {
630 0         0 $editor->{allowDecimals} = \0;
631             }
632             }
633 336         695 $editor->{maxLength} = $size;
634             }
635            
636 500 100       1223 if(keys %$editor > 0) {
637 353   100     1095 $cols->{$col}->{extra_properties} = $cols->{$col}->{extra_properties} || {};
638             $cols->{$col}->{extra_properties} = merge($cols->{$col}->{extra_properties},{
639 353         1617 editor => $editor
640             });
641             }
642             ## --
643            
644             # --vv-- NEW: handling for 'enum' columns (Github Issue #30):
645 500 0 33     1842 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 107         534 return { columns => $cols };
676             }
677              
678             sub TableSpec_valid_db_columns {
679 306     306 0 645 my $self = shift;
680              
681 306         665 my @single_rels = ();
682 306         607 my @multi_rels = ();
683 306         572 my @virtual_single_rels = ();
684              
685 306         563 my %fk_cols = ();
686 306         5492 my %pri_cols = map {$_=>1} $self->primary_columns;
  331         12419  
687              
688 306         7482 foreach my $rel ($self->relationships) {
689 585         22949 my $info = $self->relationship_info($rel);
690            
691 585         20187 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 585 100 100     5574 ! $pri_cols{$rel} #<-- exclude primary column names. TODO: this check is performed later, fix
      33        
      66        
      100        
702             );
703            
704 585 100       15928 if($accessor eq 'single') {
    100          
705 191         843 my $cond_info = $self->parse_relationship_cond($info->{cond});
706 191 50 33     853 if($cond_info->{self} && $cond_info->{foreign}) {
707 191         440 push @single_rels, $rel;
708 191         325 my ($fk) = keys %{$info->{attrs}->{fk_columns}};
  191         663  
709 191 100       698 $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 309         911 push @multi_rels, $rel;
723             }
724             }
725              
726 306         1912 $self->TableSpec_set_conf('relationship_column_names',\@single_rels);
727 306         8280 $self->TableSpec_set_conf('multi_relationship_column_names',\@multi_rels);
728 306         7291 $self->TableSpec_set_conf('relationship_column_fks_map',\%fk_cols);
729              
730             # New: move single rels up to immediately follow their FK column:
731 306 100       8161 my @cols = map { $_, ( $fk_cols{$_} ? $fk_cols{$_} : () ) } $self->columns;
  1722         9486  
732              
733 306         1374 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 199     199 0 1064 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 1223     1223 0 2001 my $self = shift;
763 1223 50       2566 die "TableSpec_set_conf(): bad arguments" unless (scalar(@_) > 0);
764            
765             # First arg can be a hashref - deref and call again:
766 1223 50       2285 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 1223         20879 $self->TableSpec_built_cnf(undef); #<-- FIXME!!
775            
776             # Special handling for setting 'column_properties':
777 1223 100       15871 if ($col_prop_names{$_[0]}) {
778 50         99 shift @_; #<-- pull out the 'column_properties' first arg
779 50         170 return $self->_TableSpec_set_column_properties(@_);
780             };
781            
782             # Enforce even number of args for good measure:
783 1173 50       2486 die join(' ',
784             'TableSpec_set_conf( %cnf ):',
785             "odd number of args in key/value list:", Dumper(\@_)
786             ) if (scalar(@_) & 1);
787            
788 1173         2861 my %cnf = @_;
789            
790 1173         2696 for my $param (keys %cnf) {
791             # Also make sure all the keys (even positions) are simple scalars:
792 1585 50       10451 die join(' ',
793             'TableSpec_set_conf( %cnf ):',
794             'found ref in key position:', Dumper($_)
795             ) if (ref($param));
796            
797 1585 100       2897 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       191 ) unless (ref($cnf{$param}) eq 'HASH');
803 57         373 $self->_TableSpec_set_column_properties($cnf{$param});
804             }
805             else {
806 1528         24028 $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   260 my $self = shift;
822 165 50       331 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       315 if(ref($_[0])) {
827 58 50 33     659 die "TableSpec_set_conf( column_properties => %cnf ): bad args" unless (
828             ref($_[0]) eq 'HASH' and
829             scalar(@_) == 1
830             );
831 58         142 return $self->_TableSpec_set_column_properties(%{$_[0]})
  58         325  
832             }
833            
834             # Enforce even number of args for good measure:
835 107 50       273 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         361 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     665 ) for (keys %cnf);
847            
848 107         522 my %valid_colnames = map {$_=>1} ($self->TableSpec_valid_db_columns);
  744         1186  
849            
850 107         216 my $col_props;
851 107   100     2107 $col_props ||= $self->TableSpec_cnf->{$_} for (@col_prop_names);
852 107   100     5833 $col_props ||= {};
853            
854 107         288 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     1053 ) and next unless ($valid_colnames{$col});
859 681         1046 $col_props->{$col} = $cnf{$col};
860             }
861            
862 107         1816 $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 1922     1922 0 18080 my $self = shift;
894 1922   50     4082 my $param = shift || return undef;
895 1922   66     5225 my $storage = shift || $self->get_built_Cnf;
896            
897             # Special: map all column prop names into 'column_properties'
898 1922 100       19950 $param = 'column_properties' if ($col_prop_names{$param});
899            
900 1922         4043 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 1922 50 66     4293 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 1922 50       6258 return wantarray ? () : undef unless (exists $storage->{$param});
    100          
933             # ---
934            
935 1178         5600 return $value;
936             }
937              
938              
939             sub TableSpec_has_conf {
940 84     84 0 195 my $self = shift;
941 84         179 my $param = shift;
942 84   33     445 my $storage = shift || $self->get_built_Cnf;
943 84 50       2404 return 1 if (exists $storage->{$param});
944 0         0 return 0;
945             }
946              
947              
948             sub TableSpec_related_class {
949 822     822 0 1114 my $self = shift;
950 822   50     1383 my $rel = shift || return undef;
951 822   50     12696 my $info = $self->relationship_info($rel) || return undef;
952 822         27691 my $relclass = $info->{class};
953            
954 822         36459 eval "require $relclass;";
955            
956             #my $relclass = $self->related_class($rel) || return undef;
957 822 50       6471 $relclass->can('TableSpec_get_conf') || return undef;
958 822         2323 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 822     822 0 1244 my $self = shift;
977 822   50     1479 my $rel = shift || return undef;
978 822   50     1502 my $param = shift || return undef;
979            
980 822   50     1874 my $relclass = $self->TableSpec_related_class($rel) || return undef;
981              
982             #return $relclass->TableSpec_get_conf($param,$relclass->TableSpec_cnf);
983 822         1997 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 1126     1126 0 1768 my $self = shift;
993 1126   50     2143 my $param = shift || return undef;
994 1126         18920 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 657     657 0 1806 my ($self,$cond,$info) = @_;
1031            
1032             return {} unless (
1033 657 50 33     3503 ref($cond) eq 'HASH' and
1034             scalar keys %$cond == 1
1035             );
1036            
1037 657         1420 my $data = {};
1038 657         1892 foreach my $i (%$cond) {
1039 1314         3737 my ($side,$col) = split(/\./,$i);
1040 1314         3346 $data->{$side} = $col;
1041             }
1042 657         1515 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   19256 no warnings 'uninitialized'; # because we might compare undef values
  5         11  
  5         4067  
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 9258 my $self = shift;
1140 183         672 my $rest_key_col = $self->TableSpec_get_conf('rest_key_column');
1141 183 50 33     625 return $rest_key_col if ($rest_key_col && $rest_key_col ne '');
1142 183         3371 my @pri = $self->primary_columns;
1143 183 50 33     8710 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 132 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         1472 );
1204            
1205             # --- Actualize/load methods into the Row object namespace:
1206 54         263 foreach my $meth (keys %RowMethods) {
1207 5     5   36 no strict 'refs';
  5         12  
  5         1087  
1208 378         736 my $meth_name = join '::', $class, $meth;
1209 378         2397 *$meth_name = subname $meth_name => $RowMethods{$meth};
1210             }
1211             # ---
1212             }
1213              
1214              
1215             sub _table_name_safe {
1216 488     488   22839 my $arg = shift;
1217            
1218 488 50 33     5264 my $table = !(ref $arg) && $arg->can('table') ? $arg->table : $arg; # class method or straight function
1219              
1220 488 50 50     2518 $table = $$table if ((ref($table)||'') eq 'SCALAR'); # Handle ScalarRef values
1221 488         2132 $table = (reverse split(/\./,$table))[0]; # Handle 'db.table' and 'schema.db.table' formats
1222              
1223 488         1508 $table =~ s/[\'\"]//g; # Strip quotes
1224 488         1114 $table =~ s/\W/_/g; # Convert any non-word characters to underscore
1225              
1226 488         1363 $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;