File Coverage

blib/lib/RapidApp/TableSpec/Role/DBIC.pm
Criterion Covered Total %
statement 650 902 72.0
branch 213 396 53.7
condition 65 151 43.0
subroutine 69 92 75.0
pod 1 47 2.1
total 998 1588 62.8


line stmt bran cond sub pod time code
1             package RapidApp::TableSpec::Role::DBIC;
2 5     5   4536 use strict;
  5         17  
  5         160  
3 5     5   32 use Moose::Role;
  5         10  
  5         43  
4 5     5   25552 use Moose::Util::TypeConstraints;
  5         19  
  5         44  
5              
6 5     5   10946 use RapidApp::TableSpec::DbicTableSpec;
  5         16  
  5         205  
7 5     5   2426 use RapidApp::TableSpec::ColSpec;
  5         20  
  5         212  
8 5     5   49 use RapidApp::TableSpec::Column::Profile;
  5         13  
  5         269  
9              
10 5     5   33 use RapidApp::Util qw(:all);
  5         9  
  5         2537  
11              
12 5     5   779 use RapidApp::DBIC::Component::TableSpec;
  5         14  
  5         219  
13              
14 5     5   2694 use DBIx::Class::Helpers 2.033003;
  5         599  
  5         228  
15             require DBIx::Class::Helper::ResultSet::Util;
16              
17 5     5   478 use DBIx::Class::_Util qw/sigwarn_silencer/;
  5         10473  
  5         346  
18              
19             require Text::Glob;
20 5     5   2176 use Text::WagnerFischer qw(distance);
  5         3777  
  5         369  
21 5     5   44 use Clone qw( clone );
  5         12  
  5         218  
22 5     5   32 use Digest::MD5 qw(md5_hex);
  5         10  
  5         296  
23 5     5   3246 use curry;
  5         1536  
  5         1468  
24              
25             # hackish performance tweak:
26             my %match_glob_cache = ();
27             sub match_glob {
28 11313     11313 0 19326 my ($l,$r) = @_;
29             $match_glob_cache{$l}{$r} = Text::Glob::match_glob($l,$r)
30 11313 100       25498 unless (exists $match_glob_cache{$l}{$r});
31 11313         209743 return $match_glob_cache{$l}{$r};
32             }
33              
34             # ---
35             # Attributes 'ResultSource', 'ResultClass' and 'schema' are interdependent. If ResultSource
36             # is not supplied to the constructor, both ResultClass and schema must be.
37             has 'ResultSource', is => 'ro', isa => 'DBIx::Class::ResultSource', lazy => 1,
38             default => sub {
39             my $self = shift;
40            
41             my $schema_attr = $self->meta->get_attribute('schema');
42             $self->meta->throw_error("'schema' not supplied; cannot get ResultSource automatically!")
43             unless ($schema_attr->has_value($self));
44            
45             #return $self->schema->source($self->ResultClass);
46             return try{$self->schema->source($self->ResultClass)} ||
47             $self->schema->source((reverse split(/\:\:/,$self->ResultClass))[0]);
48             };
49              
50             has 'ResultClass', is => 'ro', isa => 'Str', lazy => 1,
51             default => sub {
52             my $self = shift;
53             my $source_name = $self->ResultSource->source_name;
54             return $self->ResultSource->schema->class($source_name);
55             };
56              
57             has 'schema', is => 'ro', lazy => 1, default => sub { (shift)->ResultSource->schema; };
58             # ---
59              
60 5     5   50 use List::Util;
  5         12  
  5         20553  
61              
62             sub _coerce_ColSpec {
63 751     751   1411 my $v = $_[0];
64             ( # quick/dirty simulate from 'ArrayRef[Str]'
65             ref $v && ref($v) eq 'ARRAY' &&
66 751 50 33 254   8543 !( List::Util::first { ref($_) || ! defined $_ } @$v )
  254         1363  
67             ) ? RapidApp::TableSpec::ColSpec->new(colspecs => $v) : $v
68             }
69              
70             subtype 'ColSpec', as 'Object';
71             coerce 'ColSpec', from 'ArrayRef[Str]', via { &_coerce_ColSpec($_) };
72              
73             has 'include_colspec', is => 'ro', isa => 'ColSpec',
74             required => 1, coerce => \&_coerce_ColSpec, trigger => sub { (shift)->_colspec_attr_init_trigger(@_) };
75            
76             has 'updatable_colspec', is => 'ro', isa => 'ColSpec',
77             default => sub {[]}, coerce => \&_coerce_ColSpec, trigger => sub { (shift)->_colspec_attr_init_trigger(@_) };
78            
79             has 'creatable_colspec', is => 'ro', isa => 'ColSpec',
80             default => sub {[]}, coerce => \&_coerce_ColSpec, trigger => sub { (shift)->_colspec_attr_init_trigger(@_) };
81            
82             has 'always_fetch_colspec', is => 'ro', isa => 'ColSpec',
83             default => sub {[]}, coerce => \&_coerce_ColSpec, trigger => sub { (shift)->_colspec_attr_init_trigger(@_) };
84              
85             # See attr in RapidApp::Module::StorCmp::Role::DbicLnk
86             has 'no_header_transform', is => 'ro', isa => 'Bool', default => 0;
87              
88             sub _colspec_attr_init_trigger {
89 384     384   953 my ($self,$ColSpec) = @_;
90 384         11830 my $sep = $self->relation_sep;
91 384   50     1283 /${sep}/ and die "Fatal: ColSpec '$_' is invalid because it contains the relation separater string '$sep'" for ($ColSpec->all_colspecs);
92            
93             $ColSpec->expand_colspecs(sub {
94 384     384   1513 $self->expand_relspec_wildcards(\@_)
95 384         2387 });
96             }
97              
98       176 0   sub BUILD {}
99             after BUILD => sub {
100             my $self = shift;
101            
102             $self->init_relspecs;
103            
104             };
105              
106             sub init_relspecs {
107 176     176 0 352 my $self = shift;
108            
109 176         6157 $self->multi_rel_columns_indx;
110            
111             $self->include_colspec->expand_colspecs(sub {
112 176     176   887 $self->expand_relationship_columns(@_)
113 176         5497 });
114            
115             $self->include_colspec->expand_colspecs(sub {
116 176     176   763 $self->expand_related_required_fetch_colspecs(@_)
117 176         6689 });
118            
119            
120 176         6564 foreach my $col ($self->no_column_colspec->base_colspec->all_colspecs) {
121 83 100       2533 $self->Cnf_columns->{$col} = {} unless ($self->Cnf_columns->{$col});
122 83         2343 %{$self->Cnf_columns->{$col}} = (
123 83         181 %{$self->Cnf_columns->{$col}},
  83         2403  
124             no_column => \1,
125             no_multifilter => \1,
126             no_quick_search => \1
127             );
128 83         217 push @{$self->Cnf_columns_order},$col;
  83         2616  
129             }
130 176         5467 uniq($self->Cnf_columns_order);
131            
132 176         5707 my @rels = $self->include_colspec->all_rel_order;
133            
134 176         542 $self->add_related_TableSpec($_) for (grep { $_ ne '' } @rels);
  260         1029  
135            
136 176         781 $self->init_local_columns;
137            
138 176         416 foreach my $rel (@{$self->related_TableSpec_order}) {
  176         5666  
139 84         2542 my $TableSpec = $self->related_TableSpec->{$rel};
140 84         379 for my $name ($TableSpec->updated_column_order) {
141 166 50       1023 die "Column name conflict: $name is already defined (rel: $rel)" if ($self->has_column($name));
142 166         5335 $self->column_name_relationship_map->{$name} = $rel;
143             }
144             }
145            
146             }
147              
148              
149             has 'column_data_alias', is => 'ro', isa => 'HashRef', default => sub {{}};
150 118     118 0 3598 sub apply_column_data_alias { my $h = (shift)->column_data_alias; %$h = ( %$h, @_ ) }
  118         545  
151              
152             has 'no_column_colspec', is => 'ro', isa => 'ColSpec', coerce => 1, default => sub {[]};
153             sub expand_relationship_columns {
154 176     176 0 402 my $self = shift;
155 176         567 my @columns = @_;
156 176         466 my @expanded = ();
157            
158 176   50     518 my $rel_cols = $self->get_Cnf('relationship_column_names') || return;
159            
160 176         512 my @no_cols = ();
161 176         535 foreach my $col (@columns) {
162 321         689 push @expanded, $col;
163            
164 321         758 foreach my $relcol (@$rel_cols) {
165 383 100       793 next unless (match_glob($col,$relcol));
166            
167             my @add = (
168             $self->Cnf_columns->{$relcol}->{keyField},
169             $relcol . '.' . $self->Cnf_columns->{$relcol}->{displayField},
170             $relcol . '.' . $self->Cnf_columns->{$relcol}->{valueField}
171 118         3792 );
172 118         305 push @expanded, @add;
173 118         3390 $self->apply_column_data_alias( $relcol => $self->Cnf_columns->{$relcol}->{keyField} );
174 118         291 push @no_cols, grep { !$self->colspecs_to_colspec_test(\@columns,$_) } @add;
  354         894  
175             }
176             }
177 176         5901 $self->no_column_colspec->add_colspecs(@no_cols);
178            
179 176         1379 return @expanded;
180             }
181              
182             sub expand_related_required_fetch_colspecs {
183 176     176 0 380 my $self = shift;
184 176         607 my @columns = @_;
185 176         372 my @expanded = ();
186            
187 176         711 my $local_cols = $self->get_Cnf_order('columns');
188              
189 176         441 my @no_cols = ();
190 176         464 foreach my $spec (@columns) {
191 675         1263 push @expanded, $spec;
192            
193 675         1124 foreach my $col (@$local_cols) {
194 6174 100       9733 next unless (match_glob($spec,$col));
195            
196 940 50       26965 my $req = $self->Cnf_columns->{$col}->{required_fetch_colspecs} or next;
197 0 0       0 $req = [ $req ] unless (ref $req);
198            
199 0         0 my @req_columns = ();
200 0         0 foreach my $spec (@$req) {
201 0         0 my $colname = $spec;
202 0         0 my $sep = $self->relation_sep;
203 0         0 $colname =~ s/\./${sep}/g;
204 0         0 push @req_columns, $self->column_prefix . $colname;
205             #push @req_columns, $colname;
206             }
207             # This is then used later during the store read request in DbicLink2
208             $self->Cnf_columns->{$col}->{required_fetch_columns} = []
209 0 0       0 unless (defined $self->Cnf_columns->{$col}->{required_fetch_columns});
210            
211 0         0 push @{$self->Cnf_columns->{$col}->{required_fetch_columns}}, @req_columns;
  0         0  
212              
213 0         0 push @expanded, @$req;
214 0         0 push @no_cols, grep { !$self->colspecs_to_colspec_test(\@columns,$_) } @$req;
  0         0  
215             }
216             }
217 176         5688 $self->no_column_colspec->add_colspecs(@no_cols);
218              
219 176         1418 return @expanded;
220             }
221              
222              
223             sub base_colspec {
224 176     176 0 365 my $self = shift;
225 176         5476 return $self->include_colspec->base_colspec->colspecs;
226             }
227              
228             has 'Cnf_columns', is => 'ro', isa => 'HashRef', lazy => 1, default => sub {
229             my $self = shift;
230             return clone($self->get_Cnf('columns'));
231             };
232             has 'Cnf_columns_order', is => 'ro', isa => 'ArrayRef', lazy => 1, default => sub {
233             my $self = shift;
234             return clone($self->get_Cnf_order('columns'));
235             };
236              
237             sub init_local_columns {
238 176     176 0 440 my $self = shift;
239            
240 176         5254 my $class = $self->ResultClass;
241 176 50       3789 unless ( $class->primary_columns > 0 ) {
242 0     0   0 local $SIG{__WARN__} = sub {}; # GitHub Issue #167 - TODO/FIXME
243 0         0 $class->set_primary_key( $class->columns )
244             }
245            
246 176         9487 my @order = @{$self->Cnf_columns_order};
  176         5560  
247 176         822 @order = $self->filter_base_columns(@order);
248            
249 176         7000 $self->add_db_column($_,$self->Cnf_columns->{$_}) for (@order);
250             };
251              
252              
253             sub add_db_column($@) {
254 781     781 0 1641 my $self = shift;
255 781         1584 my $name = shift;
256 781 50       2643 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  781         5510  
257            
258 781 100       3383 %opt = $self->get_relationship_column_cnf($name,\%opt) if($opt{relationship_info});
259            
260 781         24653 $opt{name} = $self->column_prefix . $name;
261            
262 781         2929 my $editable = $self->filter_updatable_columns($name,$opt{name});
263 781         3919 my $creatable = $self->filter_creatable_columns($name,$opt{name});
264            
265             # -- NEW: VIRTUAL COLUMNS SUPPORT:
266 781 100       25634 if($self->ResultClass->has_virtual_column($name)) {
267             # Only editable if a custom 'set_function' has been defined for the virtual column:
268 11 100   11   567 unless(try{$self->ResultClass->column_info($name)->{set_function}}) {
  11         754  
269 4         561 $editable = 0;
270 4         11 $creatable = 0;
271             }
272             }
273             # --
274            
275             ## -- (see also below comment and commit 2dccadc6f3 which was a regression)
276             ## Hate having to do this, but we have to consider the column profiles here,
277             ## because this happens before they are applied (->add_columns, at the end of this
278             ## method). Specifically, we need to consider profiles which turn OFF allow_add
279             ## and allow_edit, otherwise, we'll clobber those settings if we want to turn
280             ## them on here. And we do need to turn them on, specifically again for the case of
281             ## creatable but not editable, because the default of editable changes the default
282             ## for creatable.
283             $creatable = 0 if List::Util::first {
284 1793     1793   4205 $RapidApp::TableSpec::Column::Profile::NO_ALLOW_ADD_PROFILES{$_}
285 781 50       37974 } @{$opt{profiles}||[]};
  781 100       4013  
286            
287             $editable = 0 if List::Util::first {
288 1793     1793   3425 $RapidApp::TableSpec::Column::Profile::NO_ALLOW_EDIT_PROFILES{$_}
289 781 50       4113 } @{$opt{profiles}||[]};
  781 100       2869  
290             ## --
291            
292            
293 781 100       2994 $opt{allow_edit} = \0 unless ($editable);
294 781 100       2204 $opt{allow_add} = \0 unless ($creatable);
295            
296             #### We do need this code after all -- see above comment. Was removed in 2dccadc6f3
297             # New: flip the allow edit/add flags on if they are not already set to something,
298             # and no_column is not set. This is needed for the case of creatable but not
299             # editable, since the default allow_add is based on the value of allow_edit, which
300             # is intended for the case of it being set by the user
301 781 100       4208 unless(jstrue($opt{no_column})) {
302 717 100 100     2465 $opt{allow_edit} //= \1 if ($editable);
303 717 100 100     2015 $opt{allow_add} //= \1 if ($creatable);
304             }
305              
306 781 100 100     3621 unless ($editable or $creatable) {
307 641 100       1652 $opt{rel_combo_field_cnf} = $opt{editor} if($opt{editor});
308 641         1630 $opt{editor} = '' ;
309             }
310              
311 781         3273 $self->add_columns(\%opt);
312             }
313              
314              
315              
316             # Load and process config params from TableSpec_cnf in the ResultClass plus
317             # additional defaults:
318             has 'Cnf_order', is => 'ro', isa => 'HashRef', default => sub {{}};
319 352     352 0 10505 sub get_Cnf_order { (shift)->Cnf_order->{$_[0]} }
320              
321              
322             has 'Cnf', is => 'ro', lazy => 1, default => sub {
323             my $self = shift;
324             my $class = $self->ResultClass;
325            
326             # Load the TableSpec Component on the Result Class if it isn't already:
327             # (should this be done like this? this is a global change and could be an overreach)
328             unless($class->can('TableSpec_cnf')) {
329             $class->load_components('+RapidApp::DBIC::Component::TableSpec');
330             $class->apply_TableSpec;
331             }
332            
333             my $cf = $class->get_built_Cnf;
334            
335             #%{$self->Cnf_order} = %{ $cf->{order} || {} };
336             #return $cf->{data} || {};
337            
338             # Legacy/backcompat: simulate the olf TableSpec_cnf format:
339             my $sim_order = { columns => [ keys %{$cf->{columns}} ] };
340            
341             %{$self->Cnf_order} = %{ $sim_order || {} };
342             return $cf || {};
343             }, isa => 'HashRef';
344 528     528 0 15121 sub get_Cnf { (shift)->Cnf->{$_[0]} }
345              
346             has 'relationship_column_configs', is => 'ro', isa => 'HashRef', lazy_build => 1;
347             sub _build_relationship_column_configs {
348 0     0   0 my $self = shift;
349            
350 0         0 my $class = $self->ResultClass;
351 0 0       0 return {} unless ($class->can('TableSpec_cnf'));
352            
353 0         0 my %rel_cols_indx = map {$_=>1} @{$self->get_Cnf('relationship_column_names')};
  0         0  
  0         0  
354 0         0 my %columns = $class->TableSpec_get_conf('columns');
355 0         0 return { map { $_ => $columns{$_} } grep { $rel_cols_indx{$_} } keys %columns };
  0         0  
  0         0  
356             };
357              
358              
359             # colspecs that were added solely for the relationship columns
360             # get stored in 'added_relationship_column_relspecs' and are then
361             # hidden in DbicLink2.
362             # TODO: come up with a better way to handle this. It's ugly.
363             has 'added_relationship_column_relspecs' => (
364             is => 'rw', isa => 'ArrayRef', default => sub {[]},
365             #trigger => sub { my ($self,$val) = @_; uniq($val) }
366             );
367              
368              
369             sub expand_relspec_wildcards {
370 752     752 0 1822 my $self = shift;
371 752         1320 my $colspec = shift;
372            
373 752 100       1846 if(ref($colspec) eq 'ARRAY') {
374 384         748 my @exp = ();
375 384         1300 push @exp, $self->expand_relspec_wildcards($_,@_) for (@$colspec);
376 384         1354 return @exp;
377             }
378            
379 368   66     11735 my $Source = shift || $self->ResultSource;
380 368         861 my @ovr_macro_keywords = @_;
381            
382             # Exclude colspecs that start with #
383 368 50       955 return () if ($colspec =~ /^\#/);
384            
385 368         1182 my @parts = split(/\./,$colspec);
386 368 100       1544 return ($colspec) unless (@parts > 1);
387            
388 85         191 my $clspec = pop @parts;
389 85         194 my $relspec = join('.',@parts);
390            
391             # There is nothing to expand if the relspec doesn't contain wildcards:
392 85 100       418 return ($colspec) unless ($relspec =~ /[\*\?\[\]\{]/);
393            
394 1         5 push @parts,$clspec;
395            
396 1         4 my $rel = shift @parts;
397 1 50       2 my $pre; { my ($match) = ($rel =~ /^(\!)/); $rel =~ s/^(\!)//; $pre = $match ? $match : ''; }
  1         3  
  1         4  
  1         2  
  1         6  
398            
399 1         9 my @rel_list = $Source->relationships;
400             #scream($_) for (map { $Source->relationship_info($_) } @rel_list);
401            
402 1         28 my @macro_keywords = @ovr_macro_keywords;
403 1         3 my $macro; {
404 1         3 my ($match) = ($rel =~ /^\{([\?\:a-zA-Z0-9]+)\}/);
  1         3  
405 1         3 $rel =~ s/^\{([\?\:a-zA-Z0-9]+)\}//;
406 1         2 $macro = $match;
407             }
408 1 50       17 push @macro_keywords, split(/\:/,$macro) if ($macro);
409 1         4 my %macros = map { $_ => 1 } @macro_keywords;
  0         0  
410            
411 1 0 0     3 my @accessors = grep { $_ eq 'single' or $_ eq 'multi' or $_ eq 'filter'} @macro_keywords;
  0         0  
412 1 50       4 if (@accessors > 0) {
413 0         0 my %ac = map { $_ => 1 } @accessors;
  0         0  
414 0         0 @rel_list = grep { $ac{ $Source->relationship_info($_)->{attrs}->{accessor} } } @rel_list;
  0         0  
415             }
416              
417 1         4 my @matching_rels = grep { match_glob($rel,$_) } @rel_list;
  2         7  
418             die 'Invalid ColSpec: "' . $rel . '" doesn\'t match any relationships of ' .
419             $Source->schema->class($Source->source_name)
420 1 50 33     19 unless ($macros{'?'} or @matching_rels > 0 or scalar(@rel_list) == 0);
      33        
421            
422 1         5 my @expanded = ();
423 1         4 foreach my $rel_name (@matching_rels) {
424 2         14 my @suffix = $self->expand_relspec_wildcards(join('.',@parts),$Source->related_source($rel_name),@ovr_macro_keywords);
425 2         18 push @expanded, $pre . $rel_name . '.' . $_ for (@suffix);
426             }
427              
428 1         6 return (@expanded);
429             }
430              
431              
432             has 'relation_sep' => ( is => 'ro', isa => 'Str', required => 1 );
433             has 'relspec_prefix' => ( is => 'ro', isa => 'Str', default => '' );
434             # needed_join is the relspec_prefix in DBIC 'join' attr format
435             has 'needed_join' => ( is => 'ro', isa => 'HashRef', lazy => 1, default => sub {
436             my $self = shift;
437             return {} if ($self->relspec_prefix eq '');
438             return $self->chain_to_hash(split(/\./,$self->relspec_prefix));
439             });
440             has 'column_prefix' => ( is => 'ro', isa => 'Str', lazy => 1, default => sub {
441             my $self = shift;
442             return '' if ($self->relspec_prefix eq '');
443             my $col_pre = $self->relspec_prefix;
444             my $sep = $self->relation_sep;
445             $col_pre =~ s/\./${sep}/g;
446             return $col_pre . $self->relation_sep;
447             });
448              
449              
450              
451              
452             around 'get_column' => sub {
453             my $orig = shift;
454             my $self = shift;
455             my $name = shift;
456            
457             my $rel = $self->column_name_relationship_map->{$name};
458             if ($rel) {
459             my $TableSpec = $self->related_TableSpec->{$rel};
460             return $TableSpec->get_column($name) if ($TableSpec);
461             }
462            
463             return $self->$orig($name);
464             };
465              
466              
467             # accepts a list of column names and returns the names that match the base colspec
468             sub filter_base_columns {
469 176     176 0 396 my $self = shift;
470 176         675 my @columns = @_;
471            
472             # Why has this come up?
473             # filter out columns with invalid characters (*):
474 176         498 @columns = grep { /^[A-Za-z0-9\-\_\.]+$/ } @columns;
  1403         3472  
475            
476 176         809 return $self->colspec_select_columns({
477             colspecs => $self->base_colspec,
478             columns => \@columns,
479             });
480             }
481              
482             sub filter_include_columns {
483 781     781 0 1297 my $self = shift;
484 781         1660 my @columns = @_;
485            
486 781         23770 my @inc_cols = $self->colspec_select_columns({
487             colspecs => $self->include_colspec->colspecs,
488             columns => \@columns,
489             });
490            
491 781         32787 my @rel_cols = $self->colspec_select_columns({
492             colspecs => $self->added_relationship_column_relspecs,
493             columns => \@columns,
494             });
495            
496 781         2455 my %allowed = map {$_=>1} @inc_cols,@rel_cols;
  688         2588  
497 781         1693 return grep { $allowed{$_} } @columns;
  1376         3762  
498             }
499              
500             # accepts a list of column names and returns the names that match updatable_colspec
501             sub filter_updatable_columns {
502 784     784 0 1357 my $self = shift;
503 784         2220 my @columns = @_;
504            
505             #exclude all multi relationship columns (except new m2m multi rel columns)
506             @columns = grep {
507 784         1724 $self->m2m_rel_columns_indx->{$self->column_prefix . $_} ||
508 1565 100       47055 !$self->multi_rel_columns_indx->{$self->column_prefix . $_}
509             } @columns;
510            
511 784         23963 return $self->colspec_select_columns({
512             colspecs => $self->updatable_colspec->colspecs,
513             columns => \@columns,
514             });
515             }
516              
517              
518              
519             # accepts a list of column names and returns the names that match creatable_colspec
520             sub filter_creatable_columns {
521 781     781 0 1843 my $self = shift;
522 781         2299 my @columns = @_;
523            
524             #exclude all multi relationship columns
525             #@columns = grep {!$self->multi_rel_columns_indx->{$_}} @columns;
526            
527             #exclude all multi relationship columns (except new m2m multi rel columns)
528             @columns = grep {
529 781         1722 $self->m2m_rel_columns_indx->{$self->column_prefix . $_} ||
530 1562 100       52598 !$self->multi_rel_columns_indx->{$self->column_prefix . $_}
531             } @columns;
532              
533             # First filter by include_colspec:
534 781         2582 @columns = $self->filter_include_columns(@columns);
535            
536 781         27867 return $self->colspec_select_columns({
537             colspecs => $self->creatable_colspec->colspecs,
538             columns => \@columns,
539             });
540             }
541              
542              
543              
544             # Tests whether or not the colspec in the second arg matches the colspec of the first arg
545             # The second arg colspec does NOT expand wildcards, it has to be a specific rel/col string
546             sub colspec_to_colspec_test {
547 1683     1683 0 2300 my $self = shift;
548 1683         2203 my $colspec = shift;
549 1683         2150 my $test_spec = shift;
550            
551 1683         2588 my ($match) = ($colspec =~ /^(\!)/); $colspec =~ s/^(\!)//;
  1683         2346  
552 1683 50       2672 my $x = $match ? -1 : 1;
553            
554 1683         3209 my @parts = split(/\./,$colspec);
555 1683         2896 my @test_parts = split(/\./,$test_spec);
556 1683 100       4290 return undef unless(scalar @parts == scalar @test_parts);
557            
558 841         1338 foreach my $part (@parts) {
559 1151 50       2178 my $test = shift @test_parts or return undef;
560 1151 100       2025 return undef unless (match_glob($part,$test));
561             }
562            
563 472         1090 return $x;
564             }
565              
566             sub colspecs_to_colspec_test {
567 354     354 0 559 my $self = shift;
568 354         478 my $colspecs = shift;
569 354         594 my $test_spec = shift;
570            
571 354 50       1082 $colspecs = [ $colspecs ] unless (ref($colspecs) eq 'ARRAY');
572            
573 354         503 my $match = 0;
574 354         660 foreach my $colspec (@$colspecs) {
575 1683   100     2899 my $result = $self->colspec_to_colspec_test($colspec,$test_spec) || next;
576 472 50       904 return 0 if ($result < 0);
577 472 50       1047 $match = 1 if ($result > 0);
578             }
579            
580 354         1068 return $match;
581             }
582              
583              
584              
585             my %dist_cache = ();
586             sub get_distance {
587 649     649 0 1320 my ($l,$r) = @_;
588 649 100       2436 $dist_cache{$l}{$r} = distance($l,$r) unless (exists $dist_cache{$l}{$r});
589 649         129471 return $dist_cache{$l}{$r};
590             }
591              
592              
593             #around colspec_test => &func_debug_around();
594              
595             # TODO:
596             # abstract this logic (much of which is redundant) into its own proper class
597             # (merge with Mike's class)
598             # Tests whether or not the supplied column name matches the supplied colspec.
599             # Returns 1 for positive match, 0 for negative match (! prefix) and undef for no match
600             sub _colspec_test($$){
601 8183     8183   12229 my $self = shift;
602 8183   50     15148 my $full_colspec = shift || die "full_colspec is required";
603 8183   50     13967 my $col = shift || die "col is required";
604            
605             # @other_colspecs - optional.
606             # If supplied, the column will also be tested against the colspecs in @other_colspecs,
607             # and no match will be returned unless this colspec matches *and* has the lowest
608             # edit distance of any other matches. This logic is designed so that remaining
609             # colspecs to be tested can be considered, and only the best match will win. This
610             # is meaningful when determining things like order based on a list of colspecs. This
611             # doesn't serve any purpose when doing a straight bool up/down test
612             # tested with
613 8183         12335 my @other_colspecs = @_;
614            
615            
616 8183         11070 my $full_colspec_orig = $full_colspec;
617 8183         14013 my ($neg_flag) = ($full_colspec =~ /^(\!)/); $full_colspec =~ s/^(\!)//;
  8183         11925  
618 8183 50       13736 my $x = $neg_flag ? -1 : 1;
619 8183 50       13149 my $match_return = $neg_flag ? 0 : 1;
620            
621 8183         17469 my @parts = split(/\./,$full_colspec);
622 8183         13055 my $colspec = pop @parts;
623 8183         13998 my $relspec = join('.',@parts);
624              
625 8183         244963 my $sep = $self->relation_sep;
626 8183         12767 my $prefix = $relspec;
627 8183         12513 $prefix =~ s/\./${sep}/g;
628            
629 8183         19836 @parts = split(/${sep}/,$col);
630 8183         14186 my $test_col = pop @parts;
631 8183         13792 my $test_prefix = join($sep,@parts);
632            
633             # no match:
634 8183 100       30022 return undef unless ($prefix eq $test_prefix);
635            
636             # match (return 1 or 0):
637 3603 100       6845 if (match_glob($colspec,$test_col)) {
638             # Calculate WagnerFischer edit distance
639 649         1465 my $distance = get_distance($colspec,$test_col);
640            
641             # multiply my $x to set the sign, then flip so bigger numbers
642             # mean better match instead of the reverse
643 649         1338 my $value = $x * (1000 - $distance); # <-- flip
644            
645 649         1302 foreach my $spec (@other_colspecs) {
646 0 0       0 my $other_val = $self->colspec_test($spec,$col) or next;
647              
648             # A colspec in @other_colspecs is a better match than us, so we defer:
649 0 0       0 return undef if (abs $other_val > abs $value);
650             }
651 649         2977 return $value;
652             };
653            
654             # no match:
655 2954         13125 return undef;
656             }
657              
658             #
659             # colspec_test_key is used to see if _colspec_test changed, this is
660             # the only relevant indicator to refetch the result for the given
661             # colspec_test
662             #
663 5     5   47 use B::Deparse;
  5         13  
  5         43103  
664             our $colspec_test_source;
665             {
666             my $deparse = B::Deparse->new;
667             $colspec_test_source = $deparse->coderef2text(\&_colspec_test);
668             }
669              
670             # New: caching wrapper for performance:
671             sub colspec_test($$){
672 9260     9260 0 17268 my ( $self, @args ) = @_;
673 9260         17594 my $colspec_key = join('|',@args);
674 9260   100     28935 return $self->{_colspec_test_cache}{$colspec_key} //= $self->_colspec_test(@args);
675             }
676              
677             # returns a list of loaded column names that match the supplied colspec set
678             sub get_colspec_column_names {
679 0     0 0 0 my $self = shift;
680 0         0 my @colspecs = @_;
681 0 0       0 @colspecs = @{$_[0]} if (ref($_[0]) eq 'ARRAY');
  0         0  
682            
683             # support for passing colspecs with relspec wildcards:
684 0         0 @colspecs = $self->expand_relspec_wildcards(\@colspecs,undef,'?');
685            
686 0         0 return $self->colspec_select_columns({
687             colspecs => \@colspecs,
688             columns => [ $self->updated_column_order ]
689             });
690             }
691              
692             # returns a list of all loaded column names except those that match the supplied colspec set
693             sub get_except_colspec_column_names {
694 0     0 0 0 my $self = shift;
695            
696 0         0 my %colmap = map { $_ => 1} $self->get_colspec_column_names(@_);
  0         0  
697 0         0 return grep { ! $colmap{$_} } $self->updated_column_order;
  0         0  
698             }
699              
700             # Tests if the supplied colspec set matches all of the supplied columns
701             sub colspec_matches_columns {
702 0     0 0 0 my $self = shift;
703 0         0 my $colspecs = shift;
704 0         0 my @columns = @_;
705 0         0 my @matches = $self->colspec_select_columns({
706             colspecs => $colspecs,
707             columns => \@columns
708             });
709 0 0       0 return 1 if (@columns == @matches);
710 0         0 return 0;
711             }
712              
713             our $colspec_select_columns_source;
714              
715             # Returns a sublist of the supplied columns that match the supplied colspec set.
716             # The colspec set is considered as a whole, with each column name tested against
717             # the entire compiled set, which can contain both positive and negative (!) colspecs,
718             # with the most recent match taking precidence.
719             sub colspec_select_columns {
720 3303     3303 0 6435 my $self = shift;
721 3303 50       8943 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  3303         12305  
722              
723             $self->{_colspec_select_columns_cache} = {}
724 3303 100       9806 unless defined $self->{_colspec_select_columns_cache};
725              
726 3303 50       7875 my $colspecs = $opt{colspecs} or die "colspec_select_columns(): expected 'colspecs'";
727 3303 50       7019 my $columns = $opt{columns} or die "colspec_select_columns(): expected 'columns'";
728 3303         4733 $columns = [ sort { $a cmp $b } @{$columns} ];
  5780         13903  
  3303         11393  
729              
730             my $colspec_select_columns_key = join('_',
731 3303         16802 md5_hex(join('_',@{$colspecs})),
732 3303         5841 md5_hex(join('_',@{$columns})),
  3303         12879  
733             );
734              
735 1296         4787 return @{$self->{_colspec_select_columns_cache}{$colspec_select_columns_key}}
736 3303 100       10878 if defined $self->{_colspec_select_columns_cache}{$colspec_select_columns_key};
737              
738 2007         3054 my $cache_key;
739 2007 100       62443 if ($self->has_cache) {
740 1465         83206 $cache_key = join('_','colspec_select_columns_cache',
741             md5_hex($colspec_test_source.$colspec_select_columns_source),
742             md5_hex($self->ResultClass),
743             $colspec_select_columns_key,
744             );
745 1465         37078 my $cache_content = $self->cache->get($cache_key);
746 1465 100       405754 return @{$self->{_colspec_select_columns_cache}{$colspec_select_columns_key} = $cache_content}
  685         4776  
747             if $cache_content;
748             }
749              
750             # if best_match_look_ahead is true, the current remaining colspecs will be passed
751             # to each invocation of colspec_test which will cause it to only return a match
752             # when testing the *closest* (according to WagnerFischer edit distance) colspec
753             # of the set to the column. This prevents
754 1322         3492 my $best_match = $opt{best_match_look_ahead};
755            
756 1322 50       3458 $colspecs = [ $colspecs ] unless (ref $colspecs);
757 1322 50       2852 $columns = [ $columns ] unless (ref $columns);
758            
759 1322 50       4204 $opt{match_data} = {} unless ($opt{match_data});
760              
761 1322         3071 my %match = map { $_ => 0 } @$columns;
  3092         7925  
762 1322         2775 my @order = ();
763 1322         2066 my $i = 0;
764 1322         2974 for my $spec (@$colspecs) {
765 4349         13335 my @remaining = @$colspecs[++$i .. $#$colspecs];
766 4349         8048 for my $col (@$columns) {
767              
768 9260         17543 my @arg = ($spec,$col);
769 9260 50       16139 push @arg, @remaining if ($best_match); # <-- push the rest of the colspecs after the current for index
770            
771 9260 100       16883 my $result = $self->colspec_test(@arg) or next;
772 1726 50       4252 push @order, $col if ($result > 0);
773 1726         2870 $match{$col} = $result;
774             $opt{match_data}->{$col} = {
775             index => $i - 1,
776             colspec => $spec
777 1726 100       7579 } unless ($opt{match_data}->{$col});
778             }
779             }
780            
781 1322         3774 my $colspec_select_columns = [ uniq(grep { $match{$_} > 0 } @order) ];
  1726         5625  
782 1322 100       3372 if ($cache_key) {
783 780         22546 $self->cache->set($cache_key,$colspec_select_columns);
784             }
785 1322         902764 return @{$self->{_colspec_select_columns_cache}{$colspec_select_columns_key} = $colspec_select_columns};
  1322         10487  
786             }
787              
788             {
789             my $deparse = B::Deparse->new;
790             $colspec_select_columns_source = $deparse->coderef2text(\&colspec_select_columns);
791             }
792              
793              
794              
795             sub _order_columns_by_result_source1 {
796 0     0   0 my ($self, $rsource, @columns) = @_;
797            
798 0         0 my @local;
799             my %relmap;
800 0         0 for my $col (@columns) {
801 0         0 my ($rel,$remote) = split($self->relation_sep,$col,2);
802 0 0       0 if($remote) {
803 0   0     0 my $arr = $relmap{$rel} ||= [];
804 0         0 push @$arr,$remote;
805             }
806             else {
807 0         0 push @local, $col;
808             }
809             }
810            
811 0         0 my %loc = map {$_=>1} @local;
  0         0  
812 0         0 my @new_order = grep { $loc{$_} } ( $rsource->columns, $rsource->relationships );
  0         0  
813            
814 0         0 for my $rel ( grep { $relmap{$_} } $rsource->relationships ) {
  0         0  
815 0         0 my $rel_source = $rsource->related_source($rel);
816             push @new_order,
817 0         0 map { join($self->relation_sep,$rel,$_) }
818 0         0 $self->_order_columns_by_result_source($rel_source,@{$relmap{$rel}});
  0         0  
819              
820             }
821            
822 0         0 return uniq(@new_order)
823             }
824              
825              
826              
827             sub _order_columns_by_result_source {
828 176     176   678 my ($self, $rsource, @columns) = @_;
829            
830 176         421 my @local;
831             my %relmap;
832 176         462 for my $col (@columns) {
833 947         27560 my ($rel,$remote) = split($self->relation_sep,$col,2);
834 947 100       2346 if($remote) {
835 166   100     685 my $arr = $relmap{$rel} ||= [];
836 166         488 push @$arr,$remote;
837             }
838             else {
839 781         1781 push @local, $col;
840             }
841             }
842            
843 176         443 my %loc = map {$_=>1} @local;
  781         1668  
844 176         957 my @new_order = grep { $loc{$_} } ( $rsource->columns, $rsource->relationships );
  1516         5579  
845            
846 176         575 for my $rel ( grep { $relmap{$_} } $rsource->relationships ) {
  366         1368  
847 84         370 my $rel_source = $rsource->related_source($rel);
848             push @new_order,
849 162         4927 map { join($self->relation_sep,$rel,$_) }
850 84         13306 $self->_order_columns_by_result_source($rel_source,@{$relmap{$rel}});
  84         381  
851              
852             }
853            
854 176         711 return uniq(@new_order)
855             }
856              
857              
858              
859              
860             sub apply_natural_column_order {
861 92     92 0 234 my $self = shift;
862            
863 92         378 $self->apply_natural_column_order_new;
864             #$self->apply_natural_column_order_old;
865            
866             }
867              
868             sub apply_natural_column_order_new {
869 92     92 0 222 my $self = shift;
870            
871             my @new_order = $self->_order_columns_by_result_source(
872             $self->ResultSource,
873 92         2791 @{$self->column_order}
  92         2370  
874             );
875            
876             # Add all the current columns to the end of the new list in case any
877             # got missed. (this prevents the chance of this operation dropping any
878             # of the existing columns, dupes are filtered out below):
879 92         417 push @new_order, $self->updated_column_order;
880              
881 92         326 my %seen = ();
882 92         237 @{$self->column_order} = grep { !$seen{$_}++ } @new_order;
  92         2751  
  1551         2860  
883 92         374 return $self->updated_column_order; #<-- for good measure
884             }
885              
886              
887             # Applies the original column order defined in the table Schema:
888             sub apply_natural_column_order_old {
889 0     0 0 0 my $self = shift;
890 0         0 my $class = $self->ResultClass;
891              
892             # New: need to consult the TableSpec method now that we move single-rels up into the column
893             # list at the location of their FK column -- its no longer as simple as columns then rels
894 0 0       0 my @local = $class->can('default_TableSpec_cnf_column_order')
895             ? ( $class->default_TableSpec_cnf_column_order )
896             : ( $class->columns, $class->relationships ); # fall-back for good measure
897              
898             $self->reorder_by_colspec_list(
899 0 0       0 @local, @{ $self->include_colspec->colspecs || [] }
  0         0  
900             );
901             }
902              
903             # reorders the entire column list according to a list of colspecs. This is called
904             # by DbicLink2 to use the same include_colspec to also define the column order
905             sub reorder_by_colspec_list {
906 0     0 0 0 my $self = shift;
907 0         0 my @colspecs = @_;
908 0 0       0 @colspecs = @{$_[0]} if (ref($_[0]) eq 'ARRAY');
  0         0  
909            
910             # Check the supplied colspecs for any that don't contain '.'
911             # if there are none, and all of them contain a '.', then we
912             # need to add the base colspec '*'
913 0         0 my $need_base = 1;
914 0   0     0 ! /\./ and $need_base = 0 for (@colspecs);
915 0 0       0 unshift @colspecs, '*' if ($need_base);
916            
917 0         0 my @new_order = $self->colspec_select_columns({
918             colspecs => \@colspecs,
919             columns => [ $self->updated_column_order ],
920             best_match_look_ahead => 1
921             });
922            
923             # Add all the current columns to the end of the new list in case any
924             # got missed. (this prevents the chance of this operation dropping any
925             # of the existing columns, dupes are filtered out below):
926 0         0 push @new_order, $self->updated_column_order;
927            
928 0         0 my %seen = ();
929 0         0 @{$self->column_order} = grep { !$seen{$_}++ } @new_order;
  0         0  
  0         0  
930 0         0 return $self->updated_column_order; #<-- for good measure
931             }
932              
933             sub relation_colspecs {
934 0     0 0 0 my $self = shift;
935 0         0 return $self->include_colspec->subspec;
936             }
937              
938             sub relation_order {
939 0     0 0 0 my $self = shift;
940 0         0 return $self->include_colspec->rel_order;
941             }
942              
943              
944             sub new_TableSpec {
945 84     84 0 200 my $self = shift;
946 84         3113 return RapidApp::TableSpec::DbicTableSpec->new(@_);
947             #return RapidApp::TableSpec->with_traits('RapidApp::TableSpec::Role::DBIC')->new(@_);
948             }
949              
950              
951              
952             # Returns the TableSpec associated with the supplied column name
953             sub column_TableSpec {
954 0     0 0 0 my $self = shift;
955 0         0 my $column = shift;
956              
957 0         0 my $rel = $self->column_name_relationship_map->{$column};
958 0 0       0 unless ($rel) {
959 0         0 my %ndx = map {$_=>1}
960 0         0 keys %{$self->columns},
961 0         0 @{$self->added_relationship_column_relspecs};
  0         0  
962            
963             #scream($column,\%ndx);
964            
965 0 0       0 return $self if ($ndx{$column});
966 0         0 return undef;
967             }
968            
969 0         0 return $self->related_TableSpec->{$rel}->column_TableSpec($column);
970             }
971              
972             # Accepts a list of columns and divides them into a hash of arrays
973             # with keys of the relspec to which each set of columns belongs, with
974             # both the localized and original column names in a hashref.
975             # This logic is used in update in DbicLink2
976             sub columns_to_relspec_map {
977 0     0 0 0 my $self = shift;
978 0         0 my @columns = @_;
979 0         0 my $map = {};
980            
981 0         0 foreach my $col (@columns) {
982 0 0       0 my $TableSpec = $self->column_TableSpec($col) or next;
983 0         0 my $pre = $TableSpec->column_prefix;
984 0         0 my $local_name = $col;
985 0         0 $local_name =~ s/^${pre}//;
986 0         0 push @{$map->{$TableSpec->relspec_prefix}}, {
  0         0  
987             local_colname => $local_name,
988             orig_colname => $col
989             };
990             }
991            
992 0         0 return $map;
993             }
994              
995              
996             sub columns_to_reltree {
997 0     0 0 0 my $self = shift;
998 0         0 my @columns = @_;
999 0         0 my %map = (''=>[]);
1000 0         0 foreach my $col (@columns) {
1001 0   0     0 my $rel = $self->column_name_relationship_map->{$col} || '';
1002 0         0 push @{$map{$rel}}, $col;
  0         0  
1003             }
1004            
1005 0         0 my %tree = map {$_=>1} @{delete $map{''}};
  0         0  
  0         0  
1006             #$tree{'@' . $_} = $self->columns_to_reltree(@{$map{$_}}) for (keys %map);
1007            
1008 0         0 foreach my $rel (keys %map) {
1009 0 0       0 my $TableSpec = $self->related_TableSpec->{$rel} or die "Failed to find related TableSpec $rel";
1010 0         0 $tree{'@' . $rel} = $TableSpec->columns_to_reltree(@{$map{$rel}});
  0         0  
1011             }
1012              
1013 0         0 return \%tree;
1014             }
1015              
1016              
1017             sub walk_columns_deep {
1018 3     3 0 9 my $self = shift;
1019 3         7 my $code = shift;
1020 3         14 my @columns = @_;
1021            
1022 3         9 my $recurse = 0;
1023 3 50       45 $recurse = 1 if((caller(1))[3] eq __PACKAGE__ . '::walk_columns_deep');
1024 3 50       22 local $_{return} = undef unless ($recurse);
1025 3 50       19 local $_{rel} = undef unless ($recurse);
1026 3 50       14 local $_{depth} = 0 unless ($recurse);
1027              
1028            
1029 3         15 my %map = (''=>[]);
1030 3         17 foreach my $col (@columns) {
1031 3   50     125 my $rel = $self->column_name_relationship_map->{$col} || '';
1032 3         12 push @{$map{$rel}}, $col;
  3         16  
1033             }
1034            
1035            
1036 3         13 my @local_cols = @{delete $map{''}};
  3         16  
1037            
1038 3         171 my $pre = $self->column_prefix;
1039 3         14 my %name_map = map { my $name = $_; $name =~ s/^${pre}//; $name => $_ } @local_cols;
  3         9  
  3         42  
  3         18  
1040 3         13 local $_{name_map} = \%name_map;
1041 3         15 local $_{return} = $code->($self,@local_cols);
1042 3         10 local $_{depth} = $_{depth}; $_{depth}++;
  3         7  
1043 3         26 foreach my $rel (keys %map) {
1044 0 0       0 my $TableSpec = $self->related_TableSpec->{$rel} or die "Failed to find related TableSpec $rel";
1045 0         0 local $_{last_rel} = $_{rel};
1046 0         0 local $_{rel} = $rel;
1047 0         0 $TableSpec->walk_columns_deep($code,@{$map{$rel}});
  0         0  
1048             }
1049             }
1050              
1051              
1052              
1053              
1054             # Accepts a DBIC Row object and a relspec, and returns the related DBIC
1055             # Row object associated with that relspec
1056             sub related_Row_from_relspec {
1057 0     0 0 0 my $self = shift;
1058 0   0     0 my $Row = shift || return undef;
1059 0   0     0 my $relspec = shift || '';
1060            
1061 0         0 my @parts = split(/\./,$relspec);
1062 0   0     0 my $rel = shift @parts || return $Row;
1063 0 0       0 return $Row if ($rel eq '');
1064            
1065 0 0       0 my $info = $Row->result_source->relationship_info($rel) or die "Relationship $rel not found";
1066            
1067             # Skip unless its a single (not multi) relationship:
1068 0 0 0     0 return undef unless ($info->{attrs}->{accessor} eq 'single' || $info->{attrs}->{accessor} eq 'filter');
1069            
1070 0         0 my $Related = $Row->$rel;
1071 0         0 return $self->related_Row_from_relspec($Related,join('.',@parts));
1072             }
1073              
1074              
1075             # Is this func still used??
1076             # Like column_order but only considers columns in the local TableSpec object
1077             # (i.e. not in related TableSpecs)
1078             sub local_column_names {
1079 0     0 0 0 my $self = shift;
1080 0         0 my %seen = ();
1081 0 0       0 return grep { !$seen{$_}++ && exists $self->columns->{$_} } @{$self->column_order}, keys %{$self->columns};
  0         0  
  0         0  
  0         0  
1082             }
1083              
1084              
1085             has 'column_name_relationship_map' => ( is => 'ro', isa => 'HashRef[Str]', default => sub {{}} );
1086             has 'related_TableSpec' => ( is => 'ro', isa => 'HashRef[RapidApp::TableSpec]', default => sub {{}} );
1087             has 'related_TableSpec_order' => ( is => 'ro', isa => 'ArrayRef[Str]', default => sub {[]} );
1088             sub add_related_TableSpec {
1089 84     84 0 196 my $self = shift;
1090 84         187 my $rel = shift;
1091 84 50       332 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
1092            
1093             die "There is already a related TableSpec associated with the '$rel' relationship - " . Dumper(caller_data_brief(20,'^RapidApp')) if (
1094 84 50       2668 defined $self->related_TableSpec->{$rel}
1095             );
1096            
1097 84 50       2458 my $info = $self->ResultClass->relationship_info($rel) or die "Relationship '$rel' not found.";
1098 84         5377 my $relclass = $info->{class};
1099              
1100 84         2852 my $relspec_prefix = $self->relspec_prefix;
1101 84 100 66     378 $relspec_prefix .= '.' if ($relspec_prefix and $relspec_prefix ne '');
1102 84         228 $relspec_prefix .= $rel;
1103            
1104 84         1380 my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($relclass->table);
1105 84         2838 my %params = (
1106             name => $table,
1107             ResultClass => $relclass,
1108             schema => $self->schema, #<-- need both ResultClass and schema to identify ResultSource
1109             relation_sep => $self->relation_sep,
1110             relspec_prefix => $relspec_prefix,
1111             include_colspec => $self->include_colspec->get_subspec($rel),
1112             no_header_transform => $self->no_header_transform
1113             );
1114              
1115 84   100     2713 $params{updatable_colspec} = $self->updatable_colspec->get_subspec($rel) || [];
1116 84   50     2779 $params{creatable_colspec} = $self->creatable_colspec->get_subspec($rel) || [];
1117 84   100     2588 $params{no_column_colspec} = $self->no_column_colspec->get_subspec($rel) || [];
1118              
1119 84         895 %params = ( %params, %opt );
1120            
1121 84         2595 my $class = $self->ResultClass;
1122 84 50 33     1457 if($class->can('TableSpec_get_conf') and $class->TableSpec_has_conf('related_column_property_transforms')) {
1123 84         463 my $rel_transforms = $class->TableSpec_get_conf('related_column_property_transforms');
1124 84 50       323 $params{column_property_transforms} = $rel_transforms->{$rel} if ($rel_transforms->{$rel});
1125            
1126             # -- Hard coded default 'header' transform (2011-12-25 by HV)
1127             # If there isn't already a configured column_property_transform for 'header'
1128             # add one that appends the relspec prefix. This is currently built-in because
1129             # it is such a ubiquotous need and it is just more intuitive than creating yet
1130             # other param that will always be 'on'. I am sure there are cases where this is
1131             # not desired, but until I run across them it will just be hard coded:
1132             # * Update: Yes, we do want an option to turn this off, and now there is (2015-09-29 by HV)
1133 84 50       2752 unless($self->no_header_transform) {
1134 84 100 50 292   875 $params{column_property_transforms}->{header} ||= sub { $_ ? "$_ ($relspec_prefix)" : $_ };
  292         1431  
1135             }
1136             # --
1137            
1138             }
1139            
1140 84 50       600 my $TableSpec = $self->new_TableSpec(%params) or die "Failed to create related TableSpec";
1141            
1142 84         2878 $self->related_TableSpec->{$rel} = $TableSpec;
1143 84         182 push @{$self->related_TableSpec_order}, $rel;
  84         2565  
1144            
1145 84         628 return $TableSpec;
1146             }
1147              
1148             sub addIf_related_TableSpec {
1149 0     0 0 0 my $self = shift;
1150 0         0 my ($rel) = @_;
1151            
1152 0   0     0 my $TableSpec = $self->related_TableSpec->{$rel} || $self->add_related_TableSpec(@_);
1153 0         0 return $TableSpec;
1154             }
1155              
1156             around 'get_column' => \&_has_get_column_modifier;
1157             around 'has_column' => \&_has_get_column_modifier;
1158             sub _has_get_column_modifier {
1159 1768     1768   19233 my $orig = shift;
1160 1768         3008 my $self = shift;
1161 1768         2974 my $name = $_[0];
1162            
1163 1768         56922 my $rel = $self->column_name_relationship_map->{$name};
1164 1768         2995 my $obj = $self;
1165 1768 100       12624 $obj = $self->related_TableSpec->{$rel} if (defined $rel);
1166            
1167 1768         9885 return $obj->$orig(@_);
1168             }
1169              
1170              
1171             around 'updated_column_order' => sub {
1172             my $orig = shift;
1173             my $self = shift;
1174            
1175             my %seen = ();
1176             # Start with and preserve the column order in this object:
1177             my @order = grep { !$seen{$_}++ } @{$self->column_order};
1178            
1179             # Pull in any unseen columns from the superclass (should normally be none, except when initializing)
1180             push @order, grep { !$seen{$_}++ } $self->$orig(@_);
1181            
1182             my @rels = ();
1183             push @rels, $self->related_TableSpec->{$_}->updated_column_order for (@{$self->related_TableSpec_order});
1184            
1185             # Preserve the existing order, adding only new/unseen related columns:
1186             push @order, grep { !$seen{$_}++ } @rels;
1187            
1188             @{$self->column_order} = @order;
1189             return @{$self->column_order};
1190             };
1191              
1192              
1193              
1194              
1195             has 'multi_rel_columns_indx', is => 'ro', lazy => 1, default => sub {
1196             my $self = shift;
1197             my $list = $self->get_Cnf('multi_relationship_column_names') || [];
1198            
1199             my %indx = ();
1200             foreach my $rel (@$list) {
1201             unless($self->ResultSource->has_relationship($rel)) {
1202             warn RED.BOLD . "\n\nMulti-rel column error: '$rel' is not a valid " .
1203             "relationship of ResultSource '" . $self->ResultSource->source_name .
1204             "'\n\n" . CLEAR;
1205             next;
1206             }
1207             my $info = $self->ResultSource->relationship_info($rel) || {};
1208             my $cond = $info->{cond};
1209             my $h = $cond ? $self->ResultClass->parse_relationship_cond($cond) : {};
1210             my ($rev_relname) = (keys %{$self->ResultSource->reverse_relationship_info($rel)});
1211             $indx{$rel} = { %$h,
1212             info => $info,
1213             rev_relname => $rev_relname,
1214             relname => $rel,
1215             parent_source => $self->ResultSource->source_name
1216             };
1217             }
1218            
1219             # -- finally refactored this into simpler code above (with error handling).
1220             # Got too carried away with map!!!
1221             #my %indx = map { $_ =>
1222             # { %{$self->ResultClass->parse_relationship_cond(
1223             # $self->ResultSource->relationship_info($_)->{cond}
1224             # )},
1225             # info => $self->ResultSource->relationship_info($_),
1226             # rev_relname => (keys %{$self->ResultSource->reverse_relationship_info($_)})[0],
1227             # relname => $_
1228             # }
1229             #} @$list;
1230             # --
1231            
1232             # Add in any defined functions (this all needs to be cleaned up/refactored):
1233             $self->Cnf_columns->{$_}->{function} and $indx{$_}->{function} = $self->Cnf_columns->{$_}->{function}
1234             for (keys %indx);
1235            
1236             #scream_color(GREEN,'loading');
1237             #scream_color(GREEN.BOLD,$_,$self->Cnf_columns->{$_}) for (keys %indx);
1238            
1239             #scream(\%indx);
1240              
1241             return \%indx;
1242             }, isa => 'HashRef';
1243              
1244              
1245              
1246             =head2 resolve_dbic_colname
1247              
1248             =over 4
1249              
1250             =item Arguments: $fieldName, \%merge_join, $get_render_col (bool)
1251              
1252             =item Return Value: Valid DBIC 'select'
1253              
1254             =back
1255              
1256             Returns a value which can be added to DBIC's ->{attr}{select} in order to select the column.
1257              
1258             $fieldName is the ExtJS column name to resolve. This contains the full path to the column which
1259             may span multiple joins, for example:
1260              
1261             rel1__rel2__foo
1262              
1263             In this case, 'rel1' is a relationship of the local (top-level) source, and rel2 is a relationship
1264             of the 'rel1' source. The \%merge_join argument is passed by reference and modified to contain the
1265             join needed for the select. In the case, assuming 'foo' is an ordinary column of the 'rel2' source,
1266             the select/as/join might be the following:
1267              
1268             select : 'rel2.foo'
1269             as : 'rel1__rel2__foo' # already implied by the $fieldName
1270             join : { rel1 => 'rel2' } # merged into %merge_join
1271              
1272             However, 'foo' might not be a column in the relationship of the 'rel2' source - it might be a
1273             relationship or a virtual column. In these cases, a sub-select/query is generated for the select,
1274             which is dependent on what foo actually is. For multi-rels it is a count of the related rows while
1275             for single rels it is a select of the remote display_column. For virtual columns, it is a
1276             sub-select of whatever the 'sql' attr is set to for the given virtual_column config.
1277              
1278             =cut
1279             sub resolve_dbic_colname {
1280 46     46 1 131 my ($self, $fieldName, $merge_join, $get_render_col)= @_;
1281 46   50     233 $get_render_col ||= 0;
1282              
1283             # $rel is the alias of the last relationship name in the chain --
1284             # if $fieldName is 'rel1__rel2__rel3__blah', $rel is 'rel3'
1285             #
1286             # $col is the column name in the remote source --
1287             # if $fieldName is 'rel1__rel2__rel3__blah', $col is 'blah'
1288             #
1289             # $join is the join attr needed to get to $rel/$col
1290             # if $fieldName is 'rel1__rel2__rel3__blah', $join is { rel1 => { rel2 => 'rel3' } }
1291             # the join needs to be merged into the common %merge_join hash
1292             #
1293             # $cond_data contains details about $col when $col is a relationship (otherwise it is undef)
1294             # if $fieldName is 'rel1__rel2__rel3__blah', $cond_data contains info about
1295             # the relationship 'blah', which is a relationship of the rel3 source
1296 46         142 my ($rel,$col,$join,$cond_data) = $self->resolve_dbic_rel_alias_by_column_name($fieldName,$get_render_col);
1297              
1298 46 50 33     222 %$merge_join = %{ merge($merge_join,$join) }
  46         195  
1299             if ($merge_join and $join);
1300              
1301              
1302 46 100       142 if (!defined $cond_data) {
1303             # $col is a simple column, not a relationship, we're done:
1304 17         86 return "$rel.$col";
1305             } else {
1306              
1307             # If cond_data is defined, the relation is a multi-relation, and we need to either
1308             # join and group-by, or run a sub-query. If join-and-group-by happens twice, it
1309             # breaks COUNT() (because the number of joined rows gets multiplied) so by default
1310             # we only use sub-queries. In fact, join and group-by has a lot of problems on
1311             # MySQL and we should probably never use it.
1312 29   66     252 $cond_data->{function} = $cond_data->{function} || $self->multi_rel_columns_indx->{$fieldName};
1313            
1314             # Support for a custom aggregate function
1315 29 100       94 if (ref($cond_data->{function}) eq 'CODE') {
1316             # TODO: we should use hash-style parameters
1317 7         36 return $cond_data->{function}->($self,$rel,$col,$join,$cond_data,$fieldName);
1318             }
1319             else {
1320 22         83 my $m2m_attrs = $cond_data->{info}->{attrs}->{m2m_attrs};
1321 22 100       58 if($m2m_attrs) {
1322             # -- m2m relationship column --
1323             #
1324             # Setup the special GROUP_CONCAT render/function
1325             #
1326             # This is a partial implementation supporting "m2m" (many_to_many)
1327             # relationship columns as added by the special result class function:
1328             # __PACKAGE__->TableSpec_m2m( 'rel' => 'linkrel', 'foreignrel' );
1329             # Which needs to be used instead of the built-in __PACKAGE__->many_to_many
1330             # function. (side note: this is needed for the same reason that
1331             # DBIx::Class::IntrospectableM2M was created).
1332             #
1333             # This function renders the values as a CSV list, so it is only suitable
1334             # for many_to_many cases with a limited number of rows (e.g. roles table)
1335             # which is probably the most common scenario, but certainly not the only
1336             # one. Also, this CSV list is tied into the functioning of the m2m column
1337             # editor. It is also db-specific, and only tested is MySQL and SQLite.
1338             # All these reasons are why I say this implementation is "partial" in
1339             # its current form.
1340              
1341 7         21 my $rel_info = $m2m_attrs->{rinfo};
1342 7         25 my $rev_rel_info = $m2m_attrs->{rrinfo};
1343            
1344             # initial hard-coded example the dynamic logic was based on:
1345             #my $sql = '(' .
1346             # # SQLite Specific:
1347             # #'SELECT(GROUP_CONCAT(flags.flag,", "))' .
1348             #
1349             # # MySQL Sepcific:
1350             # #'SELECT(GROUP_CONCAT(flags.flag SEPARATOR ", "))' .
1351             #
1352             # # Generic (MySQL & SQLite):
1353             # 'SELECT(GROUP_CONCAT(flags.flag))' .
1354             #
1355             # ' FROM ' . $source->from .
1356             # ' JOIN `flags` `flags` ON customers_to_flags.flag = flags.flag' .
1357             # ' WHERE ' . $cond_data->{foreign} . ' = ' . $rel . '.' . $cond_data->{self} .
1358             #')';
1359            
1360            
1361             ### TODO: build this using DBIC (subselect_rs as_query? resultset_column ?)
1362             ### This is unfortunately database specific. It works in MySQL and SQLite, and
1363             ### should work in any database with the GROUP_CONCAT function. It doesn't work
1364             ### in PostgrSQL because it doesn't have GROUP_CONCAT. This will have to be implemented
1365             ### separately first each db. TODO: ask the storage engine for the db type and apply
1366             ### a correct version of the function:
1367             ### UPDATE: Now works with PostgreSQL - PR #150, mst++, TBSliver++
1368            
1369             # TODO: support cross-db relations
1370            
1371 7         249 local *_ = $self->schema->storage->sql_maker->curry::_quote;
1372              
1373 7         855 my $rel_table_raw = $self->schema->source($rel_info->{source})->name;
1374 7         874 my $rev_rel_table_raw = $self->schema->source($rev_rel_info->{source})->name;
1375              
1376 7         548 my $rel_table = _($rel_table_raw);
1377 7         425 my $rev_rel_table = _($rev_rel_table_raw);
1378            
1379 7         265 my $rel_alias = (reverse split(/\./,$rel_table_raw))[0];
1380 7         28 my $rev_rel_alias = (reverse split(/\./,$rev_rel_table_raw))[0];
1381            
1382 7         47 my $rel_join_col = _(join '.', $rel_alias, $rev_rel_info->{cond_info}{self});
1383 7         263 my $rev_rel_join_col = _(join '.', $rev_rel_alias, $rev_rel_info->{cond_info}{foreign});
1384            
1385 7         252 my $rev_rel_col = _(join '.', $rel_alias, $rel_info->{cond_info}{foreign});
1386 7         285 my $rel_col = _(join '.', $rel, $cond_data->{self});
1387              
1388 7         241 my $sql = do {
1389              
1390 7         245 my $sqlt_type = $self->schema->storage->sqlt_type;
1391 7         818 my $concat = do {
1392 7 50       29 if ($sqlt_type eq 'PostgreSQL') {
1393 0         0 "STRING_AGG($rev_rel_join_col, ',')"
1394             } else {
1395 7         32 "GROUP_CONCAT($rev_rel_join_col)";
1396             }
1397             };
1398 7         57 join(' ', '(',
1399             "SELECT($concat)",
1400             " FROM $rel_table",
1401             " JOIN $rev_rel_table",
1402             " ON $rel_join_col = $rev_rel_join_col",
1403             " WHERE $rev_rel_col = $rel_col",
1404             ')');
1405              
1406             };
1407              
1408 7         80 return { '' => \$sql, -as => $fieldName };
1409             }
1410             else {
1411            
1412             die '"parent_source" missing from $cond_data -- cannot correlate sub-select for "$col"'
1413 15 50       47 unless ($cond_data->{parent_source});
1414            
1415 15         514 my $p_source = $self->schema->source($cond_data->{parent_source});
1416 15         692 my $rel_attrs = $p_source->relationship_info($col)->{attrs};
1417            
1418 15         94 my $rel_rs;
1419            
1420 15 50       165 my $recent_dbic = $p_source->can('resolve_relationship_condition') ? 1 : 0;
1421            
1422             # Github Issue #95
1423 15 50       58 if(!$rel_attrs->{where}) {
1424             # correlate logic works as-is unless the relationship has a 'where'
1425 15         65 $rel_rs = $self->_correlate_rs_rel(
1426             $p_source->resultset->search_rs(undef,{ alias => $rel }),
1427             $col
1428             );
1429             }
1430             else {
1431             ##########################################################################################
1432             # If there is a 'where' we have to fall back to the old logic -- FIXME!!!
1433             ##########################################################################################
1434 0         0 my $source = $self->schema->source($cond_data->{info}{source});
1435              
1436             # $rel_rs is a resultset object for $col when $col is the name of a relationship (which
1437             # it is because we're here). We are using $rel_rs to create a sub-query for a count.
1438             # We are suppling a custom alias that is not likely to conflict with the rest of the
1439             # query.
1440             $rel_rs = $source->resultset_class
1441             ->new($source, { alias => "${col}_alias" })
1442             ->search_rs(undef,{
1443 0 0       0 %{$source->resultset_attributes || {}},
1444 0 0       0 %{$cond_data->{info}{attrs} || {}}
  0         0  
1445             });
1446              
1447             # --- Github Issue #40 ---
1448             # This was the original, manual condition generation which only supported
1449             # single-key relationship conditions (and not multi-key or CodeRef):
1450             #my $cond = { "${rel}_alias.$cond_data->{foreign}" => \[" = $rel.$cond_data->{self}"] };
1451            
1452 0         0 my $cond = do {
1453 0 0       0 if($recent_dbic) {
1454             # On recent versions on DBIC, we now have a public method to do this:
1455             $p_source->resolve_relationship_condition(
1456             rel_name => $col,
1457             foreign_alias => $rel_rs->current_source_alias,
1458             self_alias => $rel
1459             )->{condition}
1460 0         0 }
1461             else {
1462             # LEGACY - this code path only applies to relationships with a 'where' in their attrs
1463             # and on a version of DBIC prior to the addition of ->resolve_relationship_condition
1464             #
1465             # Original/old comments on this block -
1466             # This is the new way which uses DBIC's internal machinery in the proper way
1467             # and works for any multi-rel cond type, including CodeRef:
1468             # UPDATE (#68): Starting in DBIC 0.08280 this invocation is producing a
1469             # warning because it doesn't know what "${col}_alias" is
1470             # (we're declaring it as the alias in $rel_rs above). It thinks
1471             # it should be a relationship, but it is just the local ('me')
1472             # alias (from the perspective of $rel_rs)
1473             #
1474             # This is not the way _resolve_condition is supposed to be called
1475             # and this will stop working in the next major DBIC release. _resolve_condition
1476             # needs to be called with a valid relname which we do not have in this case. In
1477             # order to fix this, we need to call _resolve_condition from one rel higher so
1478             # we can pass $col as the rel. For now we are just ignoring the warning which
1479             # we know is being produced. See Github Issue #68
1480 0         0 local $SIG{__WARN__} = sigwarn_silencer(qr/\Qresolution on non-existent relationship/);
1481             $source->_resolve_condition(
1482             $cond_data->{info}{cond},
1483 0         0 $rel_rs->current_source_alias, #<-- the self alias ("${col}_alias" as set above)
1484             $rel, #<-- the foreign alias
1485             )
1486             }
1487             };
1488             # ---
1489              
1490 0         0 $rel_rs = $rel_rs->search_rs($cond);
1491             ##########################################################################################
1492             }
1493              
1494 15 50       15089 if($cond_data->{info}{attrs}{accessor} eq 'multi') {
1495             # -- standard multi relationship column --
1496             # This is where the count sub-query is generated that provides
1497             # the numeric count of related items for display in multi rel columns.
1498 15         513 return { '' => $rel_rs->count_rs->as_query, -as => $fieldName };
1499             }
1500             else {
1501             # -- NEW: virtualized single relationship column --
1502             # Returns the related display_column value as a subquery using the same
1503             # technique as the count for multi-relationship columns
1504 0         0 my $source = $self->schema->source($cond_data->{info}{source});
1505 0 0       0 my $display_column = $source->result_class->TableSpec_get_conf('display_column')
1506             or die "Failed to get display_column";
1507 0         0 return { '' => $rel_rs->get_column($display_column)->as_query, -as => $fieldName };
1508             }
1509             }
1510             }
1511             }
1512             }
1513              
1514             # Now a passthrough to the stand-alone DBIx::Class::Helpers util function:
1515             sub _correlate_rs_rel {
1516 15     15   9634 my ($self, $Rs, $rel) = @_;
1517 15         76 DBIx::Class::Helper::ResultSet::Util::correlate( $Rs, $rel )
1518             }
1519              
1520             sub resolve_dbic_rel_alias_by_column_name {
1521 46     46 0 83 my $self = shift;
1522 46         86 my $fieldName = shift;
1523 46   50     147 my $get_render_col = shift || 0;
1524            
1525             # -- applies only to relationship columns and currently only used for sort:
1526             # UPDATE: now also used for column_summaries
1527 46 50       107 if($get_render_col) {
1528 0         0 my $render_col = $self->relationship_column_render_column_map->{$fieldName};
1529 0 0       0 $fieldName = $render_col if ($render_col);
1530             }
1531             # --
1532            
1533 46         1534 my $rel = $self->column_name_relationship_map->{$fieldName};
1534 46 50       126 unless ($rel) {
1535            
1536 46         1398 my $join = $self->needed_join;
1537 46         1363 my $pre = $self->column_prefix;
1538 46         242 $fieldName =~ s/^${pre}//;
1539            
1540             # Special case for "multi" relationships... they return the related row count
1541 46         1440 my $cond_data = $self->multi_rel_columns_indx->{$fieldName};
1542 46 100       121 if ($cond_data) {
1543             # Need to manually build the join to include the rel column:
1544             # Update: we no longer add this to the join, because we use a sub-select
1545             # to query the multi-relation, and don't want a product-style join in
1546             # the top-level query.
1547             #my $rel_pre = $self->relspec_prefix;
1548             #$rel_pre .= '.' unless ($rel_pre eq '');
1549             #$rel_pre .= $name;
1550             #$join = $self->chain_to_hash(split(/\./,$rel_pre));
1551            
1552             # ---
1553             # What was the purpose of this? The above was commented out and this was added
1554             # in its place (Mike?) it doesn't seem to do anything but break multi-rel columns
1555             # when joined via several intermediate single rels. Removed 2012-07-07 by HV.
1556             #$join = $self->chain_to_hash($self->relspec_prefix)
1557             # if length $self->relspec_prefix;
1558             # ---
1559            
1560 22         94 return ('me',$fieldName,$join,$cond_data);
1561             }
1562            
1563            
1564             ## ----
1565             ## NEW: VIRTUAL COLUMNS SUPPORT (added 2012-07-06 by HV)
1566             ## Check if this column has been setup via 'add_virtual_columns' in the
1567             ## Result class and look for special attributes 'function' (higher priority)
1568             ## or 'sql' (lower priority) for virtualizing the column in the
1569             ## query. This is similar to a multi rel column, but is still a column
1570             ## and not a relationship (TODO: combine this logic with the older multi
1571             ## rel column logic)
1572 24 100       706 if ($self->ResultClass->has_virtual_column($fieldName)) {
    50          
1573 7   50     553 my $info = $self->ResultClass->column_info($fieldName) || {};
1574             my $function = $info->{function} || sub {
1575 7     7   33 my ($self,$rel,$col,$join,$cond_data2,$name2) = @_;
1576 7         34 my $sql = $info->{sql} || 'SELECT(NULL)';
1577             # also see RapidApp::DBIC::Component::VirtualColumnsExt
1578 7         30 $sql = $info->{sql}->($self->ResultClass, $col) if ref $sql eq 'CODE';
1579            
1580             # ** translate 'self.' into the relname of the current context. This
1581             # should either be 'me.' or the join name. This logic is important
1582             # to be able to have an sql snippet defined in a Result class that will
1583             # work across different join/perspectives.
1584 7         26 $sql =~ s/self\./${rel}\./g;
1585 7         21 $sql =~ s/\`self\`\./\`${rel}\`\./g; #<-- also support backtic quoted form (quote_sep)
1586             # **
1587            
1588 7         103 return { '' => \"($sql)", -as => $col };
1589 7   50     984 };
1590 7         32 $cond_data = { function => $function };
1591            
1592 7 50       29 if ($info->{join}) {
1593 0         0 my @prefix = split(/\./,$self->relspec_prefix);
1594 0         0 push @prefix, $info->{join};
1595 0         0 $join = $self->chain_to_hash(@prefix);
1596             }
1597            
1598 7         46 return ('me',$fieldName,$join,$cond_data);
1599             }
1600             ## ----
1601             ## --- NEW: Virtual Single Relationship Column (Github Issue #40)
1602             elsif($self->ResultClass->has_relationship($fieldName)){
1603 0         0 my $cnf = $self->Cnf_columns->{$fieldName};
1604 0 0 0     0 if ($cnf && $cnf->{virtualized_single_rel}) {
1605             # This is emulating the existing format being passed around and
1606             # used for relationship columns (see multi_rel_columns_indx). This
1607             # is going to be totally refactored and simplified later (also,
1608             # note that 'me' has no actual meaning and is a throwback)
1609 0         0 return ('me',$fieldName,$join,{
1610             relname => $fieldName,
1611             info => $self->ResultClass->relationship_info($fieldName),
1612             parent_source => $self->ResultSource->source_name
1613             });
1614             }
1615             }
1616             # ---
1617            
1618 17         925 return ('me',$fieldName,$join);
1619             }
1620            
1621 0         0 my $TableSpec = $self->related_TableSpec->{$rel};
1622 0         0 my ($alias,$dbname,$join,$cond_data) = $TableSpec->resolve_dbic_rel_alias_by_column_name($fieldName,$get_render_col);
1623 0 0       0 $alias = $rel if ($alias eq 'me');
1624 0         0 return ($alias,$dbname,$join,$cond_data);
1625             }
1626              
1627              
1628             # This exists specifically to handle relationship columns:
1629             has 'custom_dbic_rel_aliases' => ( is => 'ro', isa => 'HashRef', default => sub {{}} );
1630              
1631             # Updated: the last item may now be a ref in which case it will be set
1632             # as the last value instead of {}
1633             sub chain_to_hash {
1634 0     0 0 0 my $self = shift;
1635 0         0 my @chain = @_;
1636            
1637 0         0 my $hash = {};
1638 0         0 my $last;
1639              
1640 0         0 my @evals = ();
1641 0         0 my $i = 0;
1642 0         0 foreach my $item (@chain) {
1643 0         0 my $right = '{}';
1644 0         0 my $set_end = 0;
1645 0 0       0 if($i++ == 0) {
1646 0         0 $last = pop @chain;
1647 0 0       0 if(ref $last) {
1648 0         0 $right = '$last';
1649 0         0 $set_end = 1;
1650             }
1651             else {
1652             # Put it back if its not a ref:
1653 0         0 push @chain, $last;
1654             }
1655             }
1656 0         0 my $left = '$hash->{\'' . join('\'}->{\'',@chain) . '\'}';
1657 0         0 unshift @evals, $left . ' = ' . $right;
1658 0 0       0 pop @chain unless ($set_end);
1659             }
1660 0         0 eval $_ for (@evals);
1661            
1662 0         0 return $hash;
1663             }
1664              
1665              
1666             has 'relationship_column_render_column_map', is => 'ro', isa => 'HashRef', default => sub {{}};
1667             sub get_relationship_column_cnf {
1668 184     184 0 488 my $self = shift;
1669 184         433 my $rel = shift;
1670 184 50       758 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  184         1109  
1671            
1672             # New: apply profiles early so any profiles which set rel column options
1673             # are available (e.g. 'soft_rel' which sets 'auto_editor_params' -- added for #77)
1674 184 100       755 if ($opt{profiles}) {
1675 102         1043 my $o = RapidApp::TableSpec::Column::Profile->_apply_profiles_soft(\%opt);
1676 102         1311 %opt = %$o;
1677             }
1678              
1679 184 50       738 return $self->get_virtual_relationship_column_cnf($rel,\%opt) if ($opt{virtualized_single_rel});
1680 184 100       6663 return $self->get_multi_relationship_column_cnf($rel,\%opt) if ($self->multi_rel_columns_indx->{$rel});
1681            
1682 82         236 my $conf = \%opt;
1683 82 50       348 my $info = $conf->{relationship_info} or die "relationship_info is required";
1684            
1685 82         2471 my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($self->ResultClass->table);
1686 82         781 my $err_info = "rel col: " . $table . ".$rel - " . Dumper($conf);
1687            
1688 82 50       26756 die "displayField is required ($err_info)" unless (defined $conf->{displayField});
1689 82 50       393 die "valueField is required ($err_info)" unless (defined $conf->{valueField});
1690 82 50       305 die "keyField is required ($err_info)" unless (defined $conf->{keyField});
1691            
1692 82     82   8378 my $Source = try{$self->ResultSource->related_source($rel)} catch {
1693 0     0   0 warn RED.BOLD . $_ . CLEAR;
1694 0         0 return undef;
1695 82 50       1002 } or return undef;
1696              
1697            
1698             # --- Disable quick searching on rel cols with virtual display_column
1699             # If the display column of the remote result class is virtual we turn
1700             # off quick searching. This *could* be supported in the future; it would require
1701             # some special coding. It is probably not something that should be on per
1702             # default anyway, because searching on a virtual column could be slow
1703             # (see the complex HAVING stuff for multifilters)**
1704             $conf = { %$conf,
1705             # TODO: this can probably be enabled much easier now, just like column summaries (#93)
1706             # the complex 'HAVING' stuff mentioned above has since been unfactored (#51)
1707             no_quick_search => \1,
1708 82 100   82   15550 } if (try{$self->ResultSource->related_class($rel)->has_virtual_column($conf->{displayField})});
  82         4709  
1709             #
1710             # ---
1711              
1712 82         8809 my $render_col = $self->column_prefix . $rel . $self->relation_sep . $conf->{displayField};
1713 82         2438 my $key_col = $self->column_prefix . $rel . $self->relation_sep . $conf->{valueField};
1714 82         2359 my $upd_key_col = $self->column_prefix . $conf->{keyField};
1715            
1716             # -- Assume the the column profiles of the display column:
1717 82         2474 my $relTS = $self->related_TableSpec->{$rel};
1718 82 50       330 if($relTS) {
1719 82         2540 my $relconf = $relTS->Cnf_columns->{$conf->{displayField}};
1720 82   33     395 $conf->{profiles} = $relconf->{profiles} || $conf->{profiles};
1721            
1722             # New: special exception - do not assume the 'autoinc' profile which
1723             # disables add/edit for the purposes of the *local* table. This does
1724             # not apply to the relationship column context, and we need to remove
1725             # it to prevent relationship columns with auto_increment display_column
1726             # from being forced read-only. This is a bit hackish - TODO/FIXME
1727 82         307 @{$conf->{profiles}} = grep { $_ ne 'autoinc' } @{$conf->{profiles}}
  416         909  
  82         277  
1728 82 50       311 if($conf->{profiles});
1729             }
1730             # --
1731            
1732 82         2445 my $colname = $self->column_prefix . $rel;
1733            
1734             # --
1735             # Store the render column that is associated with this relationship column
1736             # Currently we use this for sorting on relationship columns:
1737 82         3027 $self->relationship_column_render_column_map->{$colname} = $render_col;
1738             # Also store in the column itself - added for excel export - is this redundant to above? probably. FIXME
1739 82         259 $conf->{render_column} = $render_col;
1740             # --
1741              
1742 82         220 my $rows;
1743             my $read_raw_munger = sub {
1744 0     0   0 $rows = (shift)->{rows};
1745 0 0       0 $rows = [ $rows ] unless (ref($rows) eq 'ARRAY');
1746 0         0 foreach my $row (@$rows) {
1747 0 0       0 $row->{$colname} = $row->{$upd_key_col} if (exists $row->{$upd_key_col});
1748             }
1749 82         492 };
1750            
1751 82         374 my $required_fetch_columns = [
1752             $render_col,
1753             $key_col,
1754             $upd_key_col
1755             ];
1756            
1757 82 50       385 $conf->{renderer} = 'Ext.ux.showNull' unless ($conf->{renderer});
1758            
1759             # ---
1760             # We need to set 'no_fetch' to prevent DbicLink2 trying to fetch the rel name
1761             # as a column -- EXCEPT if the rel name is ALSO a column name:
1762 82 100       2507 my $is_also_local_col = $self->ResultSource->has_column($rel) ? 1 : 0;
1763 82 100       1012 $conf->{no_fetch} = 1 unless ($is_also_local_col);
1764             # ---
1765            
1766            
1767 82         1076 $conf = { %$conf,
1768            
1769             #no_quick_search => \1,
1770             #no_multifilter => \1,
1771            
1772             query_id_use_column => $upd_key_col,
1773             query_search_use_column => $render_col,
1774            
1775             #required_fetch_colspecs => [],
1776            
1777             required_fetch_columns => $required_fetch_columns,
1778            
1779             read_raw_munger => RapidApp::Handler->new( code => $read_raw_munger ),
1780             #update_munger => RapidApp::Handler->new( code => $update_munger ),
1781             };
1782            
1783 82         1705 my $cur_renderer = $conf->{renderer};
1784            
1785 82         198 my $is_phy = $conf->{is_phy_colname};
1786            
1787             # NEW: use simpler DbicRelRestRender to generate a REST link. Check to make sure
1788             # the relationship references the *single* primary column of the related row
1789 82         163 my $use_rest = 1; #<-- simple toggle var
1790 82     82   614 my $cond_data = try{$self->ResultClass->parse_relationship_cond($info->{cond})};
  82         4882  
1791 82     82   1617 my $rel_rest_key = try{$self->ResultSource->related_class($rel)->getRestKey};
  82         4622  
1792 82 100 33     1942 if($use_rest && $cond_data && $rel_rest_key && $conf->{open_url}) {
      33        
      66        
1793             # Toggle setting the 'key' arg in the link (something/1234 vs something/key/1234)
1794 80 50       324 my $rest_key = $rel_rest_key eq $cond_data->{foreign} ? undef : $cond_data->{foreign};
1795             $conf->{renderer} = jsfunc(
1796             'function(value, metaData, record) { return Ext.ux.RapidApp.DbicRelRestRender({' .
1797             'value:value,record:record,' .
1798             'key_col: "' . $key_col . '",' .
1799             'render_col: "' . $render_col . '",' .
1800 80 50       983 'open_url: "' . $conf->{open_url} . '"' .
    50          
1801             ( $rest_key ? ',rest_key:"' . $rest_key . '"' : '') .
1802             ( $is_phy ? ',is_phy_colname: true' : '') .
1803             '})}',$cur_renderer
1804             );
1805             }
1806             # Fall back to the older loadCnf inlineLink:
1807             else {
1808             $conf->{renderer} = jsfunc(
1809             'function(value, metaData, record, rowIndex, colIndex, store) {' .
1810             'return Ext.ux.RapidApp.DbicSingleRelationshipColumnRender({' .
1811             'value:value,metaData:metaData,record:record,rowIndex:rowIndex,colIndex:colIndex,store:store,' .
1812             'render_col: "' . $render_col . '",' .
1813             'key_col: "' . $key_col . '",' .
1814             'upd_key_col: "' . $upd_key_col . '"' .
1815 2 50       25 ( $conf->{open_url} ? ",open_url: '" . $conf->{open_url} . "'" : '' ) .
1816             '});' .
1817             '}', $cur_renderer
1818             );
1819             }
1820            
1821            
1822             ############# ---
1823 82   50     498 $conf->{editor} = $conf->{editor} || {};
1824 82   50     312 $conf->{auto_editor_params} = $conf->{auto_editor_params} || {};
1825            
1826             # ----
1827             # Set allowBlank according to the db schema of the key column. This is handled
1828             # automatically in normal columns in the profile stuff, but has to be done special
1829             # for relationship columns:
1830 82 50       2629 my $cinfo = exists $conf->{keyField} ? $self->ResultSource->column_info($conf->{keyField}) : undef;
1831 82 50 33     1876 if($cinfo and defined $cinfo->{is_nullable} and ! exists $conf->{editor}->{allowBlank}) {
      33        
1832             # This logic is specific instead of being a blanket boolean choice. If there is some other,
1833             # different, unexpected value for 'is_nullable', don't set allowBlank one way or the other
1834 82 100       368 $conf->{editor}->{allowBlank} = \0 if($cinfo->{is_nullable} == 0);
1835 82 100       272 if($cinfo->{is_nullable} == 1) {
1836 38         123 $conf->{editor}->{allowBlank} = \1;
1837             # This setting will only have an effect if the editor is AppCombo2 based:
1838 38         127 $conf->{editor}->{allowSelectNone} = \1;
1839             }
1840             }
1841             # same for 'default_value', if defined (again, this logic already happens for normal columns):
1842 82 50 33     470 $conf->{editor}->{value} = $cinfo->{default_value} if ($cinfo && exists $cinfo->{default_value});
1843             # TODO: refactor so the 'normal' column logic from 'profiles' etc gets applied here so this
1844             # duplicate logic isn't needed
1845             # ----
1846              
1847 82   50     312 $conf->{auto_editor_params} = $conf->{auto_editor_params} || {};
1848              
1849              
1850 82         205 my $aet = $conf->{auto_editor_type};
1851 82 100 66     642 if($aet eq 'combo' || $aet eq 'dropdown') {
    50          
    50          
1852            
1853             my $params = {
1854             valueField => $conf->{valueField},
1855             displayField => $conf->{displayField},
1856             name => $colname,
1857             ResultSet => $Source->resultset,
1858             record_pk => $conf->{valueField},
1859             # Optional custom ResultSet params applied to the dropdown query
1860             RS_condition => $conf->{RS_condition} ? $conf->{RS_condition} : {},
1861             RS_attr => $conf->{RS_attr} ? $conf->{RS_attr} : {},
1862 10 50       94 };
    50          
1863            
1864 10 50       4816 $params->{type_filter} = 1 if ($aet eq 'combo');
1865            
1866 10         371 my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($self->ResultClass->table);
1867 10         45 my $module_name = 'combo_' . $table . '_' . $colname;
1868             my $Module = $self->get_or_create_rapidapp_module( $module_name,
1869             class => 'RapidApp::Module::DbicCombo',
1870 10         53 params => { %$params, %{ $conf->{auto_editor_params} } }
  10         108  
1871             );
1872            
1873 10 50       79 if($conf->{editor}) {
1874 10 50       45 if($conf->{editor}->{listeners}) {
1875 0         0 my $listeners = delete $conf->{editor}->{listeners};
1876 0         0 $Module->add_listener( $_ => $listeners->{$_} ) for (keys %$listeners);
1877             }
1878 10 50       24 $Module->apply_extconfig(%{$conf->{editor}}) if (keys %{$conf->{editor}} > 0);
  10         397  
  10         56  
1879             }
1880            
1881 10         120 $conf->{editor} = $Module->content;
1882             }
1883            
1884             elsif($aet eq 'grid') {
1885            
1886             die "display_columns is required with 'grid' auto_editor_type"
1887 0 0       0 unless (defined $conf->{display_columns});
1888            
1889 0   0 0   0 my $custOnBUILD = $conf->{auto_editor_params}->{onBUILD} || sub{};
1890             my $onBUILD = sub {
1891 0     0   0 my $self = shift;
1892 0         0 $self->apply_to_all_columns( hidden => \1 );
1893 0         0 $self->apply_columns_list($conf->{display_columns},{ hidden => \0 });
1894 0         0 return $custOnBUILD->($self);
1895 0         0 };
1896 0         0 $conf->{auto_editor_params}->{onBUILD} = $onBUILD;
1897            
1898 0         0 my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($self->ResultClass->table);
1899 0         0 my $grid_module_name = 'grid_' . $table . '_' . $colname;
1900             my $GridModule = $self->get_or_create_rapidapp_module( $grid_module_name,
1901             class => 'RapidApp::Module::DbicGrid',
1902             params => {
1903             ResultSource => $Source,
1904             include_colspec => [ '*', '{?:single}*.*' ],
1905             #include_colspec => [ ($conf->{valueField},$conf->{displayField},@{$conf->{display_columns}}) ],
1906             title => '',
1907 0         0 %{ $conf->{auto_editor_params} }
  0         0  
1908             }
1909             );
1910            
1911 0 0       0 my $title = $conf->{header} ? 'Select ' . $conf->{header} : 'Select Record';
1912             $conf->{editor} = {
1913              
1914             # These can be overridden
1915             header => $conf->{header},
1916             win_title => $title,
1917             win_height => 450,
1918             win_width => 650,
1919            
1920 0         0 %{$conf->{editor}},
1921            
1922             # These can't be overridden
1923             name => $colname,
1924             xtype => 'datastore-app-field',
1925             valueField => $conf->{valueField},
1926             displayField => $conf->{displayField},
1927 0         0 load_url => $GridModule->base_url,
1928            
1929             };
1930             }
1931            
1932             elsif($aet eq 'custom') {
1933            
1934             # Use whatever is already in 'editor' plus some sane defaults
1935 72 50       357 my $title = $conf->{header} ? 'Select ' . $conf->{header} : 'Select Record';
1936             $conf->{editor} = {
1937              
1938             # These can be overridden
1939             header => $conf->{header},
1940             win_title => $title,
1941             win_height => 450,
1942             win_width => 650,
1943             valueField => $conf->{valueField},
1944             displayField => $conf->{displayField},
1945             name => $colname,
1946            
1947 72         282 %{$conf->{auto_editor_params}},
1948 72         276 %{$conf->{editor}},
  72         641  
1949             };
1950             }
1951             ############# ---
1952              
1953 82         2157 return (name => $colname, %$conf);
1954             }
1955              
1956              
1957             sub get_multi_relationship_column_cnf {
1958 102     102 0 293 my $self = shift;
1959 102         240 my $rel = shift;
1960 102 50       474 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  102         1154  
1961            
1962             return $self->get_m2m_multi_relationship_column_cnf($rel,\%opt)
1963 102 100       538 if ($opt{relationship_cond_data}->{attrs}->{m2m_attrs});
1964            
1965 101         308 my $conf = \%opt;
1966            
1967 101         2626 my $rel_data = clone($conf->{relationship_cond_data});
1968            
1969             ## -- allow override of the associated TabsleSpec cnfs from the relationship attrs:
1970 101 50       555 $conf->{title_multi} = delete $rel_data->{attrs}->{title_multi} if ($rel_data->{attrs}->{title_multi});
1971 101 50       448 $conf->{multiIconCls} = delete $rel_data->{attrs}->{multiIconCls} if ($rel_data->{attrs}->{multiIconCls});
1972 101 50       498 $conf->{open_url_multi} = delete $rel_data->{attrs}->{open_url_multi} if ($rel_data->{attrs}->{open_url_multi});
1973 101 50       346 $conf->{open_url_multi_rs_join_name} = delete $rel_data->{attrs}->{open_url_multi_rs_join_name} if ($rel_data->{attrs}->{open_url_multi_rs_join_name});
1974 101         267 delete $rel_data->{attrs}->{cascade_copy};
1975 101         250 delete $rel_data->{attrs}->{cascade_delete};
1976 101         203 delete $rel_data->{attrs}->{join_type};
1977 101         232 delete $rel_data->{attrs}->{accessor};
1978            
1979             $rel_data->{attrs}->{join} = [ $rel_data->{attrs}->{join} ] if (
1980             defined $rel_data->{attrs}->{join} and
1981 101 50 33     458 ref($rel_data->{attrs}->{join}) ne 'ARRAY'
1982             );
1983            
1984 101 50       331 if($rel_data->{attrs}->{join}) {
1985 0         0 @{$rel_data->{attrs}->{join}} = grep { $_ ne $conf->{open_url_multi_rs_join_name} } @{$rel_data->{attrs}->{join}};
  0         0  
  0         0  
  0         0  
1986 0 0       0 delete $rel_data->{attrs}->{join} unless (scalar @{$rel_data->{attrs}->{join}} > 0);
  0         0  
1987             }
1988            
1989            
1990 101 50       412 my $title = $conf->{title_multi} ? $conf->{title_multi} : 'Related "' . $rel . '" Rows';
1991            
1992             my $loadCfg = {
1993             title => $title,
1994             iconCls => $conf->{multiIconCls} ,
1995             autoLoad => {
1996             url => $conf->{open_url_multi},
1997 101         877 params => {}
1998             }
1999             };
2000            
2001             my $div_open =
2002             '<div' .
2003 101 100       756 ( $conf->{multiIconCls} ? ' class="with-inline-icon ' . $conf->{multiIconCls} . '"' : '' ) . '><span>' .
2004             $title .
2005             '&nbsp;<span class="superscript-navy">';
2006            
2007 101         255 my $cur_renderer = $conf->{renderer};
2008            
2009 101         3534 my $open_url = $self->ResultClass->TableSpec_get_conf('open_url');
2010 101     101   863 my $rel_rest_key = try{$self->ResultClass->getRestKey};
  101         6660  
2011 101 50       5063 my $orgnCol = $rel_rest_key ? join('',$self->column_prefix,$rel_rest_key) : undef;
2012            
2013 101   50     743 $conf->{required_fetch_columns} ||= [];
2014 101 50       402 push @{$conf->{required_fetch_columns}}, $orgnCol if ($orgnCol);
  101         359  
2015            
2016 101 50       3207 my $rSelfCol = $rel_data->{self} ? join('',$self->column_prefix,$rel_data->{self}) : undef;
2017 101 100 50     786 push @{$conf->{required_fetch_columns}}, $rSelfCol if ($rSelfCol && $rSelfCol ne ($orgnCol || ''));
  3   66     14  
2018              
2019             # Allow old apps to turn off using this source as a rest origin and force fallback to
2020             # the fugly, original loadCnf inlineLink
2021             my $use_rest =
2022             $rel_data->{attrs}{allow_rel_rest_origin}
2023 101   33 101   914 // try{$rel_data->{class}->TableSpec_get_conf('allow_rel_rest_origin')};
  101         3581  
2024            
2025 101 50       1766 $use_rest = 1 unless (defined $use_rest);
2026 101 100 33     816 if($use_rest && $orgnCol && $open_url) {
      66        
2027 96         1004 $conf->{renderer} = jsfunc(
2028             'function(value, metaData, record) { return Ext.ux.RapidApp.DbicRelRestRender({' .
2029             'value:value,record:record,' .
2030             "disp: '" . $div_open . "' + value + '</span>'," .
2031             'key_col: "' . $orgnCol . '",' .
2032             'open_url: "' . $open_url . '",' .
2033             'multi_rel: true,' .
2034             'rs: "' . $rel . '"' .
2035             '})}',$cur_renderer
2036             );
2037             }
2038             else {
2039              
2040             # Fall back to the old thick, ugly loadCnf inlineLink:
2041             # This code path should never happen with RapidDbic, but will still happen for
2042             # manual setups where there is no 'open_url', other missing TableSpec data,
2043             # or the fallback 'allow_rel_rest_origin => 0' has been set
2044            
2045             ## -----
2046             ## This code block generates the 'resultset_attr' json in perl instead of
2047             ## in ExtJS (Ext.encode) so we can reach in and prevent the character sequence ']}'
2048             ## which occurs if the last *value* of a hash (Object) happens to be an array
2049             ## which only sometimes occurs (and, not consistently across restarts). The bug
2050             ## this created was in the compile of Ext.XTemplate, because they use {[ ... ]} as
2051             ## markers for inline javascript, so this would cause Syntax Exception.
2052             ## The exact JavaScript logic was attempted to be reproduced, just to try to keep
2053             ## the behavior as close to the way it was. Note that this codepath itself is
2054             ## legacy and only happens in special cases (and then the bug was a special case
2055             ## of a special case). This is probably the most ugly code in the whole codebase.
2056 5         15 my $join_name = $conf->{open_url_multi_rs_join_name};
2057 5         13 my $personality = $join_name;
2058 5         11 my $attr_json = do {
2059 5         44 my $attr = clone($rel_data->{attrs});
2060 5   50     47 $attr->{join} ||= [];
2061 5         11 push @{$attr->{join}}, $join_name;
  5         18  
2062 5         11 @{$attr->{join}} = grep { $_ ne 'me' } @{$attr->{join}};
  5         13  
  5         18  
  5         14  
2063            
2064 5 50 33     18 $personality = $attr->{join}[0] if $attr->{join}[0] && $join_name eq 'me';
2065            
2066 5 50       13 if(scalar(@{$attr->{join}}) == 0) { delete $attr->{join}; }
  5 0       16  
  5         16  
2067 0         0 elsif(scalar(@{$attr->{join}}) == 1) { $attr->{join} = $attr->{join}[0]; }
  0         0  
2068            
2069 5         44 my $json = encode_json_utf8($attr);
2070            
2071 5         368 $json =~ s/\{\[/\{ \[/g;
2072 5         13 $json =~ s/\]\}/\] \}/g;
2073            
2074 5         16 $json
2075             };
2076             ## -----
2077            
2078             $conf->{renderer} = $rel_data->{self} ? jsfunc(
2079             'function(value, metaData, record, rowIndex, colIndex, store) {' .
2080             "var div_open = '$div_open';" .
2081             "var disp = div_open + value + '</span>';" .
2082            
2083             #'var key_key = ' .
2084             'var key_val = record && record.data ? record.data["' . $rSelfCol . '"] : null;' .
2085            
2086             #'var attr = ' . RapidApp::JSON::MixedEncoder::encode_json($rel_data->{attrs}) . ';' .
2087            
2088             ( # TODO: needs to be generalized better
2089             $conf->{open_url_multi} ?
2090             'if(key_val && value && value > 0 && !Ext.ux.RapidApp.NO_DBIC_REL_LINKS) {' .
2091             'var loadCfg = ' . RapidApp::JSON::MixedEncoder::encode_json($loadCfg) . ';' .
2092            
2093             'var join_name = "' . $join_name . '";' .
2094            
2095             'var cond = {};' .
2096 5 50       65 'cond[join_name + ".' . $rel_data->{foreign} . '"] = key_val;' .
    50          
2097            
2098             #'var attr = {};' .
2099             #'if(join_name != "me"){ if(!attr.join) { attr.join = []; } attr.join.push(join_name); }' .
2100            
2101             # Fix!!!
2102             #'if(join_name == "me" && Ext.isArray(attr.join) && attr.join.length > 0) { join_name = attr.join[0]; }' .
2103            
2104             #Fix!! -- Note that 'personality' is for a specific legacy app
2105             'loadCfg.autoLoad.params.personality = "' . $personality . '";' .
2106            
2107             #'loadCfg.autoLoad.params.base_params = Ext.encode({' .
2108             # 'resultset_condition: Ext.encode(cond),' .
2109             # 'resultset_attr: Ext.encode(attr)' .
2110             #'});' .
2111            
2112             'loadCfg.autoLoad.params.base_params_base64 = base64.encode(Ext.encode({' .
2113             'resultset_condition: Ext.encode(cond),' .
2114             #'resultset_attr: Ext.encode(attr)' .
2115             "resultset_attr: '" . $attr_json . "'" .
2116             '}));' .
2117            
2118             'var href = "#loadcfg:" + Ext.urlEncode({data: Ext.encode(loadCfg)});' .
2119             'disp += "&nbsp;" + Ext.ux.RapidApp.inlineLink(' .
2120             'href,"<span>open</span>","ra-nav-link ra-icon-magnify-tiny",null,"Open/view: " + loadCfg.title' .
2121             ');' .
2122             '}'
2123             :
2124             ''
2125             ) .
2126             "disp += '</span></div>';" .
2127             'return disp;' .
2128             '}', $cur_renderer
2129             ) : jsfunc(
2130             # New: skip all the above open link logic in advance if we don't have
2131             # self/foreign rel data. Added for Github Issue #40 now that it is
2132             # possible for it to be missing (just means there will be no open link):
2133             join("\n",
2134             'function(value, metaData, record, rowIndex, colIndex, store) {',
2135             "var div_open = '$div_open';",
2136             "return div_open + value + '</span></span></div>';",
2137             '}'
2138             )
2139             );
2140             }
2141            
2142              
2143 101         3230 $conf->{name} = join('',$self->column_prefix,$rel);
2144            
2145 101         2275 return %$conf;
2146             }
2147              
2148             has 'm2m_rel_columns_indx', is => 'ro', isa => 'HashRef', default => sub {{}};
2149              
2150             sub get_m2m_multi_relationship_column_cnf {
2151 1     1 0 3 my $self = shift;
2152 1         1 my $rel = shift;
2153 1 50       5 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  1         7  
2154            
2155 1         12 my $conf = \%opt;
2156            
2157 1         5 $conf->{no_quick_search} = \1;
2158 1         3 $conf->{no_summary} = \1;
2159            
2160 1         9 $conf->{renderer} = jsfunc 'Ext.ux.RapidApp.prettyCsvRenderer';
2161            
2162 1         4 my $m2m_attrs = $conf->{relationship_cond_data}->{attrs}->{m2m_attrs};
2163 1         3 my $rinfo = $m2m_attrs->{rinfo};
2164 1         3 my $rrinfo = $m2m_attrs->{rrinfo};
2165            
2166 1         34 my $colname = $self->column_prefix . $rel;
2167 1         3 $conf->{name} = $colname;
2168            
2169 1         33 $self->m2m_rel_columns_indx->{$colname} = 1;
2170            
2171             ### This is the initial editor type 'multi-check-combo' which is only suitable if
2172             ### there are a relatively limited number of remote linkable rows (such as roles)
2173             ### TODO: add more types (like combo vs grid in single relationship combos) such
2174             ### as one that is paged and can support lots of rows to select from
2175            
2176             ### Also, TODO: add support for different diplayField and valueField. This will
2177             ### require setting up a whole additional relationship for rendering. Also, need
2178             ### to add the ability to customize the render mode. Currently it is hard coded to
2179             ### csv list of key/link values. It will always have to be something like this, but
2180             ### it could render differently. If there are many values, there might be a better way
2181             ### to render/display, such as a count like the default regular multi rel column
2182            
2183 1         32 my $schema = $self->ResultSource->schema;
2184 1         13 my $Source = $schema->source($rrinfo->{source});
2185            
2186 1         114 my $table = RapidApp::DBIC::Component::TableSpec::_table_name_safe($self->ResultClass->table);
2187 1         6 my $module_name = 'm2mcombo_' . $table . '_' . $colname;
2188             my $Module = $self->get_or_create_rapidapp_module( $module_name,
2189             class => 'RapidApp::Module::DbicCombo',
2190             params => {
2191             valueField => $rrinfo->{cond_info}->{foreign},
2192             displayField => $rrinfo->{cond_info}->{foreign},
2193             name => $colname,
2194             ResultSet => $Source->resultset,
2195             record_pk => $rrinfo->{cond_info}->{foreign},
2196             # Optional custom ResultSet params applied to the dropdown query
2197             RS_condition => $conf->{RS_condition} ? $conf->{RS_condition} : {},
2198             RS_attr => $conf->{RS_attr} ? $conf->{RS_attr} : {},
2199             #%{ $conf->{auto_editor_params} },
2200             }
2201 1 50       10 );
    50          
2202 1         49 $Module->apply_extconfig( xtype => 'multi-check-combo' );
2203            
2204 1   50     16 $conf->{editor} = $conf->{editor} || {};
2205            
2206             # allowBlank per-default. There are no database-level rules for "nullable" since the
2207             # column is virtual and has no schema/properties
2208 1 50       7 $conf->{editor}->{allowBlank} = \1 unless (exists $conf->{editor}->{allowBlank});
2209            
2210 1 50       5 if($conf->{editor}->{listeners}) {
2211 0         0 my $listeners = delete $conf->{editor}->{listeners};
2212 0         0 $Module->add_listener( $_ => $listeners->{$_} ) for (keys %$listeners);
2213             }
2214 1 50       19 $Module->apply_extconfig(%{$conf->{editor}}) if (keys %{$conf->{editor}} > 0);
  1         41  
  1         8  
2215            
2216 1         13 $conf->{editor} = $Module->content;
2217            
2218 1         40 return %$conf;
2219             }
2220              
2221              
2222             # TODO: consolidate/simplify all "virtual" relationship columns here. Multi-relationship
2223             # columns are themselves a virtual column...
2224             sub get_virtual_relationship_column_cnf {
2225 0     0 0 0 my $self = shift;
2226 0         0 my $rel = shift;
2227 0 0       0 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
2228            
2229 0         0 my $conf = {
2230             %opt,
2231             name => join('',$self->column_prefix,$rel)
2232             };
2233            
2234 0         0 my $cur_renderer = $conf->{renderer};
2235            
2236 0     0   0 my $rel_rest_key = try{$self->ResultClass->getRestKey};
  0         0  
2237 0 0       0 my $orgnCol = $rel_rest_key ? join('',$self->column_prefix,$rel_rest_key) : undef;
2238            
2239 0   0     0 $conf->{required_fetch_columns} ||= [];
2240 0 0       0 push @{$conf->{required_fetch_columns}}, $orgnCol if ($orgnCol);
  0         0  
2241              
2242 0         0 my $use_rest = 1;
2243 0 0 0     0 if($use_rest && $orgnCol) {
2244 0         0 my $open_url = $self->ResultClass->TableSpec_get_conf('open_url');
2245 0         0 $conf->{renderer} = jsfunc( join('',
2246             'function(value, metaData, record) { return Ext.ux.RapidApp.DbicRelRestRender({',
2247             'value:value,',
2248             'record:record,',
2249             'key_col: "',$orgnCol,'",',
2250             'open_url: "',$open_url,'",',
2251             'rs: "',$rel,'"',
2252             '})}'
2253             ),$cur_renderer);
2254             }
2255            
2256 0         0 return %$conf;
2257             }
2258              
2259              
2260             sub get_or_create_rapidapp_module {
2261 11     11 0 471 my $self = shift;
2262 11 50       49 my $name = shift or die "get_or_create_rapidapp_module(): Missing module name";
2263 11 50       92 my %opt = (ref($_[0]) eq 'HASH') ? %{ $_[0] } : @_; # <-- arg as hash or hashref
  0         0  
2264              
2265 11 50       116 my $rootModule = RapidApp->_rootModule or die "Failed to find RapidApp Root Module!!";
2266            
2267 11 100       469 $rootModule->apply_init_modules( tablespec => 'RapidApp::Module' )
2268             unless ( $rootModule->has_module('tablespec') );
2269            
2270 11         75 my $TMod = $rootModule->Module('tablespec');
2271            
2272 11 100       369 $TMod->apply_init_modules( $name => \%opt ) unless ( $TMod->has_module($name) );
2273            
2274 11         60 my $Module = $TMod->Module($name);
2275 11         110 $Module->call_ONREQUEST_handlers;
2276 11         484 $Module->DataStore->call_ONREQUEST_handlers;
2277            
2278 11         166 return $Module;
2279             }
2280              
2281             1;