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