File Coverage

blib/lib/Catalyst/Plugin/RapidApp/RapidDbic.pm
Criterion Covered Total %
statement 15 20 75.0
branch 2 4 50.0
condition 1 3 33.3
subroutine 5 5 100.0
pod n/a
total 23 32 71.8


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::RapidApp::RapidDbic;
2 4     4   2147600 use Moose::Role;
  4         10  
  4         29  
3 4     4   18217 use namespace::autoclean;
  4         8  
  4         39  
4              
5             with 'Catalyst::Plugin::RapidApp::TabGui';
6              
7 4     4   1207 use RapidApp::Util qw(:all);
  4         12  
  4         1848  
8             require Module::Runtime;
9             require Catalyst::Utils;
10 4     4   30 use List::Util;
  4         10  
  4         5748  
11             require Module::Runtime;
12              
13              
14             before 'setup_components' => sub {
15             my $c = shift;
16            
17             $c->config->{'Plugin::RapidApp::RapidDbic'} ||= {};
18             my $config = $c->config->{'Plugin::RapidApp::RapidDbic'};
19             $config->{dbic_models} ||= [];
20            
21             $config->{dbic_tree_module_name} = 'db';
22             $config->{table_class} = $config->{grid_class} if ($config->{grid_class});
23             $config->{table_class} ||= 'Catalyst::Plugin::RapidApp::RapidDbic::TableBase';
24             $config->{navcore_default_views} //= 1;
25             $config->{configs} ||= {};
26            
27             $c->config->{'Plugin::RapidApp::TabGui'} ||= {};
28             my $tgui_cnf = $c->config->{'Plugin::RapidApp::TabGui'};
29             $tgui_cnf->{navtrees} ||= [];
30            
31             # ---
32             # New: expert-only - allow apps to use a custom navtree_class, but require that
33             # it be a subclass of RapidApp::Module::DbicNavTree
34             my $navtree_class = 'RapidApp::Module::DbicNavTree';
35             if (my $class = $config->{navtree_class}) {
36             Module::Runtime::require_module( $class );
37             die join('',
38             "\nPlugin::RapidApp::RapidDbic: bad custom navtree_class '$class'\n",
39             " only modules which are subclasses of '$navtree_class' are allowed"
40             ) unless $class->isa($navtree_class);
41            
42             $navtree_class = $class;
43             }
44             # ---
45            
46             push @{$tgui_cnf->{navtrees}}, {
47             module => $config->{dbic_tree_module_name},
48             class => $navtree_class,
49             params => {
50             dbic_models => $config->{dbic_models},
51             table_class => $config->{table_class},
52             configs => $config->{configs},
53             # optional 'menu_require_role' will hide the *menu points*
54             # of each dbic model from the navtree, but access to the
55             # actual grids will still be allowed (i.e. via saved search)
56             menu_require_role => $config->{menu_require_role}
57             }
58             };
59             };
60              
61             # Validate we got a valid RapidDbic config by the end of setup_components or die:
62             after 'setup_components' => sub {
63             my $c = shift;
64            
65             my $cfg = $c->config->{'Plugin::RapidApp::RapidDbic'};
66             die "No 'Plugin::RapidApp::RapidDbic' config specified!" unless (
67             $cfg && ref($cfg) eq 'HASH' && scalar(keys %$cfg) > 0
68             );
69            
70             my $mdls = $cfg->{dbic_models};
71             die "Plugin::RapidApp::RapidDbic: No dbic_models specified!" unless (
72             $mdls && ref($mdls) eq 'ARRAY' && scalar(@$mdls) > 0
73             );
74              
75             # Quick hack: move RapidApp::CoreSchema to the end of the list, if present
76             # (this was needed after adding the local model config feature)
77             #@$mdls = ((grep { $_ ne 'RapidApp::CoreSchema' } @$mdls), 'RapidApp::CoreSchema') if (
78             # List::Util::first { $_ eq 'RapidApp::CoreSchema' } @$mdls
79             #);
80             };
81              
82             before 'setup_component' => sub {
83             my( $c, $component ) = @_;
84            
85             my $appclass = ref($c) || $c;
86             my $config = $c->config->{'Plugin::RapidApp::RapidDbic'};
87            
88             my $loc_cmp_name = $component;
89             $loc_cmp_name =~ s/^${appclass}\:\://;
90              
91             # -- New: read in optional RapidDbic config from the model itself, or from the main
92             # app config under the model's config key (i.e. "Model::DB")
93             my $local_cnf = scalar(
94             try{ $component->config ->{RapidDbic} }
95             || try{ $c->config->{$loc_cmp_name} ->{RapidDbic} }
96             );
97              
98             if($local_cnf) {
99             my ($junk,$name) = split(join('::',$appclass,'Model',''),$component,2);
100             if($name) {
101             $config->{dbic_models} ||= [];
102             push @{$config->{dbic_models}}, $name unless (
103             List::Util::first {$_ eq $name} @{$config->{dbic_models}}
104             );
105             # a config for this model specified in the main app config still takes priority:
106             $config->{configs}{$name} ||= $local_cnf;
107             $local_cnf = $config->{configs}{$name};
108             }
109             }
110             $local_cnf ||= {};
111             # --
112            
113             my %active_models = ();
114             foreach my $model (@{$config->{dbic_models}}) {
115             my ($schema,$result) = split(/\:\:/,$model,2);
116             $active_models{$appclass."::Model::".$schema}++;
117             }
118             return unless ($active_models{$component});
119            
120             # this doesn't seem to work, and why is it here?
121             #my $suffix = Catalyst::Utils::class2classsuffix( $component );
122             #my $config = $c->config->{ $suffix } || {};
123             my $cmp_config = try{$component->config} || {};
124            
125             my $cnf = { %$cmp_config, %$config };
126            
127             # Look for the 'schema_class' key, and if found assume this is a
128             # DBIC model. This is currently overly broad by design
129             my $schema_class = $cnf->{schema_class} or return;
130            
131             # We have to make sure the TableSpec component has been loaded on
132             # each Result class *early*, before 'Catalyst::Model::DBIC::Schema'
133             # gets ahold of them. Otherwise problems will happen if we try to
134             # load it later:
135             my ($model_name) = reverse split(/\:\:/,$component); #<-- educated guess, see temp/hack below
136             Module::Runtime::require_module($schema_class);
137            
138             my $lim_sources = $local_cnf->{limit_sources} ? {map{$_=>1} @{$local_cnf->{limit_sources}}} : undef;
139             my $exclude_sources = $local_cnf->{exclude_sources} || [];
140             my %excl_sources = map { $_ => 1 } @$exclude_sources;
141            
142             # Base RapidApp module path:
143             my $mod_path = join('/','',$c->module_root_namespace,'main');
144             $mod_path =~ s/\/+/\//g; #<-- strip double //
145            
146             for my $class (keys %{$schema_class->class_mappings}) {
147             my $source_name = $schema_class->class_mappings->{$class};
148            
149             next if ($lim_sources && ! $lim_sources->{$source_name});
150             next if ($excl_sources{$source_name});
151            
152             my $virtual_columns = try{$config->{configs}{$model_name}{virtual_columns}{$source_name}};
153             if ($class->can('TableSpec_cnf')) {
154             die "Cannot setup virtual columns on $class - already has TableSpec loaded"
155             if ($virtual_columns);
156             }
157             else {
158             $class->load_components('+RapidApp::DBIC::Component::TableSpec');
159             $class->add_virtual_columns(%$virtual_columns) if ($virtual_columns);
160             $class->apply_TableSpec;
161             }
162              
163             # ----
164             # *predict* (guess) what the auto-generated grid module paths will be and set
165             # the open url configs so that cross table links are able to work. this is
166             # just a stop-gap until this functionality is factored into the RapidApp API
167             # officially, somehow...
168            
169             my $module_name = lc($model_name . '_' . $source_name);
170             my $grid_url = join('/',$mod_path,$config->{dbic_tree_module_name},$module_name);
171             $class->TableSpec_set_conf(
172             priority_rel_columns => 1,
173             open_url_multi => $grid_url,
174             open_url => join('/',$grid_url,"item"),
175             );
176             # ----
177            
178             my $is_virtual = $class->_is_virtual_source;
179             my $defs_i = $is_virtual ? 'ra-icon-pg-red' : 'ra-icon-pg';
180             my $defm_i = $is_virtual ? 'ra-icon-pg-multi-red' : 'ra-icon-pg-multi';
181            
182             # Nicer defaults:
183             $class->TableSpec_set_conf(
184             title => ($class->TableSpec_get_set_conf('title') || $source_name),
185             title_multi => ($class->TableSpec_get_set_conf('title_multi') || "$source_name Rows"),
186             iconCls => ($class->TableSpec_get_set_conf('iconCls') || $defs_i),
187             multiIconCls => ($class->TableSpec_get_set_conf('multiIconCls') || $defm_i),
188             );
189            
190             # ----------------
191             # Apply some column-specific defaults:
192              
193             # Set actual column headers (this is not required but real headers are displayed nicer):
194             my %col_props = %{ $class->TableSpec_get_set_conf('column_properties') || {} };
195             for my $col ($class->columns,$class->relationships) {
196             $col_props{$col}{header} ||= $col;
197             }
198            
199             # For single-relationship columns (belongs_to) we want to hide
200             # the underlying fk_column because the relationship column name
201             # handles setting it for us. In typical RapidApps this is done manually,
202             # currently... Check for the config option globally or individually:
203             if($local_cnf->{hide_fk_columns} || $config->{hide_fk_columns}) {
204             for my $rel ( $class->relationships ) {
205             my $rel_info = $class->relationship_info($rel);
206             next unless ($rel_info->{attrs}->{accessor} eq 'single');
207             my $fk_columns = $rel_info->{attrs}->{fk_columns} || {};
208             $col_props{$_} =
209             # hides the column in the interface:
210             { no_column => \1, no_multifilter => \1, no_quick_search => \1 }
211             # exclude columns with the same name as the rel (see priority_rel_columns setting)
212             for (grep { $_ ne $rel } keys %$fk_columns);
213             }
214             }
215            
216             $class->TableSpec_set_conf( column_properties => %col_props )
217             if (keys %col_props > 0);
218             # ----------------
219            
220              
221             # --- apply TableSpec configs specified in the plugin config:
222             my $TSconfig = try{$config->{configs}->{$model_name}->{TableSpecs}->{$source_name}} || {};
223             $class->TableSpec_set_conf( $_ => $TSconfig->{$_} ) for (keys %$TSconfig);
224             # ---
225            
226             # Set the editor to use the existing grid unless auto_editor_type is already defined
227             # *in the RapidDbic plugin config itself*. This is needed to fix a load-order problem
228             # in which the TableSpec auto_editor_type could have been already set automatically to
229             # 'combo' (this is why we're not checking the actual TableSpec config itself). For the
230             # purposes of RapidDbic, we are taking over and superseding that layer of auto-generated
231             # configs already in action for TableSpecs. Also, if the auto_editor_type is set to
232             # 'grid', replace it with the custom existing grid, too:
233             if(!$TSconfig->{auto_editor_type} || $TSconfig->{auto_editor_type} eq 'grid') {
234             $class->TableSpec_set_conf(
235             auto_editor_type => 'custom',
236             auto_editor_params => {
237             xtype => 'datastore-app-field',
238             displayField => $class->TableSpec_get_set_conf('display_column'),
239             autoLoad => {
240             url => $class->TableSpec_get_set_conf('open_url_multi'),
241             params => {}
242             }
243             }
244             );
245             }
246            
247             }
248             };
249              
250             after 'setup_finalize' => sub {
251             my $c = shift;
252             $c->_rapiddbic_initialize_default_views_rows
253             };
254              
255              
256             sub _rapiddbic_initialize_default_views_rows {
257 4     4   16 my $c = shift;
258            
259 4 50       33 my $config = $c->config->{'Plugin::RapidApp::RapidDbic'} or die
260             "No 'Plugin::RapidApp::RapidDbic' config specified!";
261            
262             # If enabled and available, initialize all rows for Default Model/Source views:
263 4 50 33     462 if($config->{navcore_default_views} && $c->_navcore_enabled) {
264 0           my $rootModule = $c->model('RapidApp')->rootModule;
265            
266 0           my $AppTree = $rootModule->Module('main')->Module($config->{dbic_tree_module_name});
267 0           my @source_models = $AppTree->all_source_models;
268 0           my $Rs = $c->model('RapidApp::CoreSchema::DefaultView');
269             $Rs->find_or_create(
270             { source_model => $_ },
271             { key => 'primary' }
272 0           ) for (@source_models);
273             }
274             }
275              
276             1;
277              
278             __END__
279              
280             =head1 NAME
281              
282             Catalyst::Plugin::RapidApp::RapidDbic - Instant web front-ends for DBIx::Class
283              
284             =head1 QUICK START
285              
286             To get started very quickly with a new app, see the RapidDbic helper for bootstrapping a new app
287             in the manual:
288              
289             =over
290              
291             =item *
292              
293             L<RapidApp::Manual::Bootstrap>
294              
295             =back
296              
297             =head1 SYNOPSIS
298              
299             package MyApp;
300            
301             use Catalyst qw/ RapidApp::RapidDbic /;
302              
303             Then, also in the main Catalyst app class:
304              
305             __PACKAGE__->config(
306            
307             'Plugin::RapidApp::RapidDbic' => {
308             dbic_models => ['DB','OtherModel'],
309            
310             # All custom configs optional...
311             configs => {
312             DB => {
313             grid_params => {
314             # ...
315             },
316             TableSpecs => {
317             # ...
318             }
319             },
320             OtherModel => {
321             # ...
322             }
323             }
324             }
325             );
326              
327             Or, within individual DBIC::Schema model class(es):
328              
329             package MyApp::Model::DB;
330             use Moo;
331             extends 'Catalyst::Model::DBIC::Schema';
332              
333             __PACKAGE__->config(
334             schema_class => 'MyApp::DB',
335            
336             connect_info => {
337             # ...
338             },
339            
340             RapidDbic => {
341            
342             # All custom configs optional...
343             grid_params => {
344             # to make all grids editable:
345             '*defaults' => {
346             updatable_colspec => ['*'],
347             creatable_colspec => ['*'],
348             destroyable_relspec => ['*'],
349             # ...
350             },
351             MySourceA => {
352             persist_immediately => {
353             # Save only when clicking "Save" button...
354             create => 0, update => 0, destroy => 0
355             },
356             # ...
357             }
358             },
359             TableSpecs => {
360             MySourceA => {
361             title => 'My Source Abba!',
362             title_multi => 'Abbas',
363             iconCls => 'icon-apple',
364             multiIconCls => 'icon-apples',
365             # ...
366             },
367             SourceB => {
368             display_column => 'foo',
369             columns => {
370             foo => {
371             title => 'Foo',
372             # ...
373             }
374             }
375             }
376             },
377             table_class => 'MyApp::Module::CustGridModule',
378             virtual_columns => {
379             # ...
380             }
381             }
382              
383             =head1 DESCRIPTION
384              
385             The RapidDbic plugin provides a very high-level, abstract configuration layer for initializing
386             a structure of interfaces for accessing L<DBIC::Schema|Catalyst::Model::DBIC::Schema> models
387             for Catalyst/RapidApp. These interfaces are fully functional out-of-the-box, but also provide
388             a robust base which can be configured and extended into various forms of custom applications.
389              
390             RapidDbic itself simply assembles and configures other RapidApp plugins and modules into a useful,
391             working combination without any fuss, while also providing configuration hooks into those sub-modules
392             across different layers. This includes the L<TabGui|Catalyst::Plugin::RapidApp::TabGui> plugin for
393             the main interface and navigation structure, and sets of DBIC-aware modules such as grids, forms and
394             trees.
395              
396             This hooks into a very broad ecosystem of highly customizable and extendable modules which are
397             still in the process of being fully documented... The unconfigured, default state resembles a
398             database admin utility, with powerful CRUD features, query builder, batch modify forms, and so on.
399              
400             RapidDbic is also designed to work with other, high-level plugins to access additional turn-key
401             application-wide functionality, such as access and authorization with the
402             L<AuthCore|Catalyst::Plugin::RapidApp::AuthCore> plugin and saved user-views with the
403             L<NavCore|Catalyst::Plugin::RapidApp::NavCore> plugin.
404              
405             =head1 CONFIG
406              
407             The only required config option is specifying at least one L<DBIC::Schema|Catalyst::Model::DBIC::Schema>
408             model to enable. This can be achieved either with the C<dbic_models> option in the plugin config key
409             C<'Plugin::RapidApp::RapidDbic'> within the main Catalyst app class/config, or by specifying a C<'RapidDbic'>
410             config key in the model class(es) itself (see SYNOPSIS).
411              
412             The optional additional config options for each model are then divided into two main sections,
413             C<grid_params> and C<TableSpecs>, which are each further divided into each source name in the
414             DBIC schema.
415              
416             =head2 grid_params
417              
418             The grid_params section allows overriding the parameters to be supplied to the RapidApp module
419             which is automatically built for each source (with a menu point for each in the navtree). By default,
420             this is the grid-based module L<Catalyst::Plugin::RapidApp::RapidDbic::TableBase>, but can be changed
421             (with the C<grid_class> config option, see below) to any module extending a DBIC-aware RapidApp
422             module (which are any of the modules based on the "DbicLink" ecosystem) which doesn't even
423             necesarily need to be derived from a grid module at all...
424              
425             For convenience, the special source name C<'*defaults'> can be used to set params for all sources
426             at once.
427              
428             The DbicLink modules configuration documentation is still in-progress.
429              
430             =head2 TableSpecs
431              
432             TableSpecs are extra schema metadata which can optionally be associated with each source/columns.
433             These provide extra "hints" for how to represent the schema entities in different application
434             interface contexts. TableSpec data is passive and is consumed by all DBIC-aware RapidApp Modules
435             for building their default configuration(s).
436              
437             For a listing of the different TableSpec data-points which are available, see the TableSpec
438             documentation in the manual:
439              
440             =over
441              
442             =item *
443              
444             L<RapidApp::Manual::TableSpec>
445              
446             =back
447              
448             =head2 grid_class
449              
450             Specify a different RapidApp module class name to use for the source. The default is
451             C<Catalyst::Plugin::RapidApp::RapidDbic::TableBase>. The C<grid_params> for each source
452             are supplied to the constructor of this class to create the module instances (for each source).
453              
454             C<grid_class> can be supplied at the top of the RapidDbic config to apply to all models and
455             all sources, within the RapidDbic config at the model level to apply to all sources within
456             the given model, or in the individual source config within C<grid_params> to apply to
457             only the individual source.
458              
459             =head2 virtual_columns
460              
461             Automatically inject virtual columns via config into the sources... More documentation TDB.
462              
463             In the meantime, see the virtual_column example in the Chinook Demo:
464              
465             =over
466              
467             =item *
468              
469             L<Chinook Demo - 2.5 - Virtual Columns|http://www.rapidapp.info/demos/chinook/2_5>
470              
471             =back
472              
473             =head1 PLACK INTERFACE (QUICK START)
474              
475             There is also now a L<Plack> interface available which can be used to generate a fully working
476             RapidDbic-based app with a working config on-the-fly that can then be mounted like any Plack
477             app:
478              
479             =over
480              
481             =item *
482              
483             L<Plack::App::RapidApp::rDbic>
484              
485             =back
486              
487             For instant gratification, a script-based wrapper is also available which can fire up an app
488             with a single shell command and dsn argument:
489              
490             =over
491              
492             =item *
493              
494             L<rdbic.pl>
495              
496             =back
497              
498              
499             =head1 SEE ALSO
500              
501             =over
502              
503             =item *
504              
505             L<Chinook Demo (www.rapidapp.info)|http://www.rapidapp.info/demos/chinook>
506              
507             =item *
508              
509             L<RapidApp::Manual::RapidDbic>
510              
511             =item *
512              
513             L<RapidApp::Manual::Plugins>
514              
515             =item *
516              
517             L<Catalyst::Plugin::RapidApp>
518              
519             =item *
520              
521             L<Catalyst::Plugin::RapidApp::TabGui>
522              
523             =item *
524              
525             L<Catalyst::Plugin::RapidApp::NavCore>
526              
527             =item *
528              
529             L<Catalyst>
530              
531             =back
532              
533             =head1 AUTHOR
534              
535             Henry Van Styn <vanstyn@cpan.org>
536              
537             =head1 COPYRIGHT AND LICENSE
538              
539             This software is copyright (c) 2013 by IntelliTree Solutions llc.
540              
541             This is free software; you can redistribute it and/or modify it under
542             the same terms as the Perl 5 programming language system itself.
543              
544             =cut
545              
546              
547             1;
548