File Coverage

blib/lib/Mojolicious/Plugin/DSC.pm
Criterion Covered Total %
statement 85 85 100.0
branch 40 50 80.0
condition 23 27 85.1
subroutine 7 7 100.0
pod 1 1 100.0
total 156 170 91.7


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::DSC;
2 2     2   1408 use Mojo::Base 'Mojolicious::Plugin';
  2         2  
  2         9  
3 2     2   1353 use DBIx::Simple::Class;
  2         43005  
  2         51  
4 2     2   17 use Mojo::Util qw(camelize);
  2         2  
  2         78  
5 2     2   7 use Carp;
  2         2  
  2         1846  
6              
7             our $VERSION = '1.006';
8              
9             #some known good defaults
10             my $COMMON_ATTRIBUTES = {
11             RaiseError => 1,
12             AutoCommit => 1,
13             };
14              
15             has config => sub { {} };
16              
17             sub register {
18 21     21 1 32505 my ($self, $app, $config) = @_;
19              
20             # This stuff is executed, when the plugin is loaded
21             # Config
22 21   50     46 $config //= {};
23 21   100     56 $config->{load_classes} //= [];
24 21   100     59 $config->{DEBUG} //= ($app->mode =~ m|^dev|);
25 21   100     106 $config->{dbh_attributes} //= {};
26 21   100     50 $config->{database} //= '';
27             croak('"load_classes" configuration directive '
28             . 'must be an ARRAY reference containing a list of classes to load.')
29 21 100       146 unless (ref($config->{load_classes}) eq 'ARRAY');
30             croak('"dbh_attributes" configuration directive '
31             . 'must be a HASH reference. See DBI/Database_Handle_Attributes.')
32 20 100       128 unless (ref($config->{dbh_attributes}) eq 'HASH');
33              
34             #prepared Data Source Name?
35 19 100       30 if (!$config->{dsn}) {
36             $config->{driver}
37 11 100       323 || croak('Please choose and set a database driver like "mysql","SQLite","Pg"!..');
38 9 100       224 croak('Please set "database"!') unless $config->{database} =~ m/\w+/x;
39 7   100     14 $config->{host} ||= 'localhost';
40             $config->{dsn} = 'dbi:'
41             . $config->{driver}
42             . ':database='
43             . $config->{database}
44             . ';host='
45             . $config->{host}
46 7 100       22 . ($config->{port} ? ';port=' . $config->{port} : '');
47              
48 7 50       15 if ($config->{database} =~ m/(\w+)/x) {
49 7 100       16 $config->{namespace} = camelize($1) unless $config->{namespace};
50             }
51             }
52             else {
53             my ($scheme, $driver, $attr_string, $attr_hash, $driver_dsn) =
54             DBI->parse_dsn($config->{dsn})
55 8   66     34 || croak("Can't parse DBI DSN! dsn=>'$config->{dsn}'");
56 7         105 $config->{driver} = $driver;
57              
58 7 50       35 $scheme =~ m/(database|dbname)=\W?(\w+)/x and do {
59 7   66     22 $config->{namespace} ||= camelize($2);
60             };
61             $config->{dbh_attributes} =
62 7 50       27 {%{$config->{dbh_attributes}}, ($attr_hash ? %$attr_hash : ())};
  7         27  
63             }
64              
65 14   100     55 $config->{onconnect_do} ||= [];
66              
67             #Postpone connecting to the database till the first helper call.
68             my $helper_builder = sub {
69              
70             #ready... Go!
71             my $dbix = DBIx::Simple->connect(
72             $config->{dsn},
73             $config->{user} || '',
74             $config->{password} || '',
75 17   100 17   3221358 {%$COMMON_ATTRIBUTES, %{$config->{dbh_attributes}}}
  17   50     81  
76             );
77 17 100       17800 if (!ref($config->{onconnect_do})) {
78 1         2 $config->{onconnect_do} = [$config->{onconnect_do}];
79             }
80 17         17 for my $sql (@{$config->{onconnect_do}}) {
  17         32  
81 13 100       24 next unless $sql;
82 12 100       22 if (ref($sql) eq 'CODE') { $sql->($dbix); next; }
  8         15  
  8         97  
83 4         9 $dbix->dbh->do($sql);
84             }
85 17         1779 my $DSCS = $config->{namespace};
86 17         41 my $schema = Mojo::Util::class_to_path($DSCS);
87 17 100       111 if (eval { require $schema; }) {
  17         1279  
88 14         610 $DSCS->DEBUG($config->{DEBUG});
89 14         60 $DSCS->dbix($dbix);
90             }
91             else {
92 3         375 Carp::carp("($@) Trying to continue without $schema...");
93 3         1067 DBIx::Simple::Class->DEBUG($config->{DEBUG});
94 3         12 DBIx::Simple::Class->dbix($dbix);
95             }
96 17         84 $self->_load_classes($app, $config);
97 15         82 return $dbix;
98 14         50 };
99              
100             #Add $dbix as attribute and helper where needed
101 14   100     30 my $dbix_helper = $config->{dbix_helper} ||= 'dbix';
102 14         39 $app->helper($dbix_helper, $helper_builder);
103 14         290 $self->config({%$config}); #copy
104 14 100       132 $app->$dbix_helper() if (!$config->{postpone_connect});
105 12         68 return $self;
106             } #end register
107              
108              
109             sub _load_classes {
110 17     17   1521 my ($self, $app, $config) = @_;
111 17         17 state $load_error = <<"ERR";
112             You may need to create it first using the dsc_dump_schema.pl script.'
113             Try: dsc_dump_schema.pl --help'
114             ERR
115              
116 17 100       17 if (scalar @{$config->{load_classes}}) {
  17         37  
117 13         12 my @classes = @{$config->{load_classes}};
  13         28  
118 13         15 my $namespace = $config->{namespace};
119 13 50       34 $namespace .= '::' unless $namespace =~ /:{2}$/;
120 13         29 foreach my $class (@classes) {
121 20 100       136 if ($class =~ /^$namespace/) {
122 7         18 my $e = Mojo::Loader::load_class($class);
123 7 0       82 Carp::confess(ref $e ? "Exception: $e" : "$class not found: ($load_error)")
    50          
124             if $e;
125 7         12 next;
126             }
127 13         33 my $e = Mojo::Loader::load_class($namespace . $class);
128 13 100       2456 if (ref $e) {
    100          
129 1         3 Carp::confess("Exception: $e");
130             }
131             elsif ($e) {
132 1         2 my $e2 = Mojo::Loader::load_class($class);
133 1 0       15686 Carp::confess(ref $e2 ? "Exception: $e2" : "$class not found: ($load_error)")
    50          
134             if $e2;
135             }
136             }
137             }
138             else { #no load_classes
139 4         15 my @classes = Mojo::Loader::find_modules($config->{namespace});
140 4         971 foreach my $class (@classes) {
141 3         7 my $e = Mojo::Loader::load_class($class);
142 3 100       16709 croak($e) if $e;
143             }
144             }
145 15         20 return;
146             }
147              
148             1;
149              
150             =pod
151              
152             =encoding utf8
153              
154             =head1 NAME
155              
156             Mojolicious::Plugin::DSC - use DBIx::Simple::Class in your application.
157              
158             =head1 SYNOPSIS
159              
160             #load
161             # Mojolicious
162             $self->plugin('DSC', $config);
163              
164             # Mojolicious::Lite
165             plugin 'DSC', $config;
166            
167             my $user = My::User->find(1234);
168             #or
169             my $user = My::User->query('SELECT * FROM users WHERE user=?','ivan');
170             #or if SQL::Abstract is isnstalled
171             my $user = My::User->select(user=>'ivan');
172            
173            
174             =head1 DESCRIPTION
175              
176             Mojolicious::Plugin::DSC is a L plugin that helps you
177             use L in your application.
178             It also adds an app attribute (C<$app-Edbix>) and controller helper (C<$c-Edbix>)
179             which is a L instance.
180              
181             =head1 CONFIGURATION
182              
183             The configuration is pretty flexible:
184              
185             # in Mojolicious startup()
186             $self->plugin('DSC', {
187             dsn => 'dbi:SQLite:database=:memory:;host=localhost'
188             });
189             #or
190             $self->plugin('DSC', {
191             driver => 'mysqlPP',
192             database => 'mydbname',
193             host => '127.0.0.1',
194             user => 'myself',
195             password => 'secret',
196             onconnect_do => [
197             'SET NAMES UTF8',
198             'SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO"'
199             sub{my $dbix = shift; do_something_complicated($dbix)}
200             ],
201             dbh_attributes => {AutoCommit=>0},
202             namespace => 'My',
203            
204             #will load My::User, My::Content, My::Pages
205             load_classes =>['User', 'Content', 'My::Pages'],
206            
207             #now you can use $app->DBIX instead of $app->dbix
208             dbix_helper => 'DBIX'
209             });
210              
211             The following parameters can be provided:
212              
213             =head2 load_classes
214              
215             An ARRAYREF of classes to be loaded. If not provided,
216             all classes under L will be loaded.
217             Classes are expected to be already dumped as files using
218             C from an existing database.
219              
220             #all classes under My::Schema::Class
221             $app->plugin('DSC', {
222             namespace => My::Schema::Class,
223             });
224             #only My::Schema::Class::Groups and My::Schema::Class::Users
225             $app->plugin('DSC', {
226             namespace => My::Schema::Class,
227             load_classes => ['Groups', 'Users']
228             });
229              
230             =head2 DEBUG
231              
232             Boolean. When the current L is C this value
233             is 1.
234              
235             $app->plugin('DSC', {
236             DEBUG => 1,
237             namespace => My::Schema::Class,
238             load_classes => ['Groups', 'Users']
239             });
240              
241             =head2 dbh_attributes
242              
243             HASHREF. Attributes passed to L.
244             Default values are:
245              
246             {
247             RaiseError => 1,
248             AutoCommit => 1,
249             };
250              
251             They can be overriden:
252              
253             $app->plugin('DSC', {
254             namespace => My::Schema::Class,
255             dbh_attributes =>{ AutoCommit => 0, sqlite_unicode => 1 }
256             });
257              
258             =head2 dsn
259              
260             Connection string parsed using L and passed to L.
261              
262             From this string we guess the L, L, L, L
263             and the L which ends up as camelised form of
264             the L name.
265              
266             If L is not passed most of the configuration values above must
267             be provided so a valid connection string can be constructed.
268             If L is provided it will be preferred over the above parameters
269             (excluding namespace) because the developer should know better how
270             exactly to connect to the database.
271              
272             $app->plugin('DSC', {
273             namespace => My::Schema::Class,
274             dbh_attributes => {sqlite_unicode => 1},
275             dsn => 'dbi:SQLite:database=myfile.sqlite'
276             });
277              
278             =head2 driver
279              
280             String. One of "mysql","SQLite","Pg" etc...
281             This string is prepended with "dbi:". No default value.
282              
283             $app->plugin('DSC', {
284             driver => 'mysql',
285             dbh_attributes => {sqlite_unicode => 1},
286             dsn => 'dbi:SQLite:database=myfile.sqlite'
287             });
288              
289             =head2 database
290              
291             String - the database name. No default value.
292              
293             $app->plugin('DSC', {
294             database => app->home->rel_file('etc/ado.sqlite'),
295             dbh_attributes => {sqlite_unicode => 1},
296             driver => 'SQLite',
297             namespace => 'Ado::Model',
298             });
299              
300             =head2 host
301              
302             String. defaults to C.
303              
304             =head2 port
305              
306             String. Not added to the connection string if not provided.
307              
308             =head2 namespace
309              
310             The class name of your schema class. If not provided the value will be guessed
311             from the L or L. It is recommended to provide your
312             schema class name.
313              
314             $app->plugin('DSC', {
315             database => app->home->rel_file('etc/ado.sqlite'),
316             dbh_attributes => {sqlite_unicode => 1},
317             driver => 'SQLite',
318             namespace => 'My::Model',
319             });
320              
321              
322             =head2 user
323              
324             String. Username used to connect to the database.
325              
326             =head2 password
327              
328             String. Password used to connect to the database.
329              
330             =head2 onconnect_do
331              
332             ARRAYREF of SQL statements and callbacks which will be executed right after
333             establiching the connection.
334              
335             $app->plugin('DSC', {
336             database => app->home->rel_file('etc/ado.sqlite'),
337             dbh_attributes => {sqlite_unicode => 1},
338             driver => 'SQLite',
339             namespace => 'Ado::Model',
340             onconnect_do => [
341             'PRAGMA encoding = "UTF-8"',
342             'PRAGMA foreign_keys = ON',
343             'PRAGMA temp_store = 2', #MEMORY
344             'VACUUM',
345             sub{
346             shift->dbh->sqlite_create_function( 'now', 0, sub { return time } );
347             }
348             ],
349             });
350              
351              
352             =head2 postpone_connect
353              
354             Boolean. If set, establishing the connection to the database will
355             be postponed for the first call of C<$app-Edbix> or the method
356             name you provided for the L.
357              
358             =head2 dbix_helper
359              
360             String. The name of the helper method that can be created to invoke/use
361             directly the L instance on your controller or application.
362             Defaults to C.
363              
364             =head1 METHODS
365              
366             L inherits all methods from
367             L and implements the following new ones.
368              
369             =head2 C
370              
371             $plugin->register(Mojolicious->new);
372              
373             Register plugin in L application.
374              
375             =head2 config
376              
377             This plugin own configuration. Returns a HASHref.
378              
379             #debug
380             $app->log->debug($app->dumper($plugin->config));
381              
382             =head1 SEE ALSO
383              
384             L, L, L, L.
385              
386             =head1 LICENSE AND COPYRIGHT
387              
388             Copyright 2012 Красимир Беров (Krasimir Berov).
389              
390             This program is free software, you can redistribute it and/or
391             modify it under the terms of the Artistic License version 2.0.
392              
393             See http://dev.perl.org/licenses/ for more information.
394              
395             =cut