root/lang/perl/Catalyst-Model-Jifty-DBI/trunk/lib/Catalyst/Model/Jifty/DBI.pm @ 2067

Revision 2067, 15.3 kB (checked in by charsbar, 5 years ago)

lang/perl/Catalyst-Model-Jifty-DBI: renamed from Catalyst-Model-JDBI-Schemas

  • Property svn:eol-style set to native
Line 
1package Catalyst::Model::Jifty::DBI;
2
3use strict;
4use warnings;
5use Carp;
6use base qw( Catalyst::Model Class::Accessor::Fast );
7
8our $VERSION = '0.03';
9
10use NEXT;
11use UNIVERSAL::require;
12use Jifty::DBI::Handle;
13use Module::Find;
14
15__PACKAGE__->mk_accessors(qw( handles connect_infos schema_base ));
16
17sub new {
18  my $self = shift->NEXT::new(@_);
19
20  my $class = ref $self;
21  my $model_name = $class;
22     $model_name =~ s/^[\w:]+::(?:Model|M):://;
23
24  $self->schema_base($class) unless $self->schema_base;
25  $self->connect_infos({});
26  $self->handles({});
27
28  # let's see connect_info; we may have several.
29  if ( ref $self->{connect_info} eq 'HASH' ) {
30    $self->connect_infos->{_} = $self->{connect_info};
31
32    # if we have only one connect_info, then why don't we connect?
33    my $handle = Jifty::DBI::Handle->new;
34       $handle->connect( %{ $self->{connect_info} } );
35
36    $self->handles->{_} = $handle;
37  }
38  elsif ( ref $self->{databases} eq 'ARRAY' ) {
39    foreach my $database ( @{ $self->{databases} } ) {
40      croak "connect_info should be a hash reference"
41        unless ref $database->{connect_info} eq 'HASH';
42
43      my $name = $database->{name}
44              || $database->{connect_info}->{database};
45
46      croak "database should have a unique name"
47        if !$name || $self->connect_infos->{$name};
48
49      $self->connect_infos->{$name} = $database->{connect_info};
50    }
51    # as we may have multiple connect_info, we should just prepare
52    # and wait until we really need to connect.
53  }
54
55  no strict 'refs';
56  my $schema_base = $self->schema_base;
57  foreach my $moniker ( findsubmod $schema_base ) {
58    next if $moniker =~ /Collection$/;
59    $moniker =~ s/^$schema_base\:://;
60
61    *{"${class}::${moniker}::ACCEPT_CONTEXT"} = sub {
62      shift;
63      shift->model( $model_name )->record( $moniker );
64    };
65
66    my $collection_moniker = $moniker.'Collection';
67    *{"${class}::${collection_moniker}::ACCEPT_CONTEXT"} = sub {
68      shift;
69      shift->model( $model_name )->collection( $collection_moniker );
70    };
71  }
72  return $self;
73}
74
75sub _select_name {
76  my ($self, %options) = @_;
77
78  return $options{name} if $options{name};
79  return $options{from} if $options{from};
80  return $self->default_handle_name;
81}
82
83sub default_handle_name {
84  my $self = shift;
85
86  if ( @_ ) {
87    my $new_handle = shift;
88    unless ( $self->connect_infos->{$new_handle} ) {
89      croak "$new_handle doesn't have connect_info";
90    }
91    $self->{default_handle} = $new_handle;
92  }
93  unless ( $self->{default_handle} ) {
94    $self->{default_handle} = ( $self->databases )[0];
95  }
96  $self->{default_handle};
97}
98
99sub handle {
100  my ($self, %options) = @_;
101
102  my $name   = $self->_select_name(%options);
103  my $handle = $self->handles->{$name};
104
105  unless ( $handle and $handle->dbh and $handle->dbh->{Active} ) {
106    my $connect_info = $self->connect_infos->{$name};
107    croak "database $name doesn't have connect_info"
108      unless ref $connect_info eq 'HASH';
109
110    $handle = Jifty::DBI::Handle->new;
111    $handle->connect( %$connect_info );
112    $self->handles->{$name} = $handle;
113  }
114  $handle;
115}
116
117sub disconnect {
118  my ($self, %options) = @_;
119
120  my $name   = $self->_select_name(%options);
121  my $handle = $self->handles->{$name};
122
123  if ( $handle and $handle->dbh ) {
124    $handle->disconnect if $handle->dbh->{Active};
125  }
126  else {
127    carp "database $name doesn't exist or open";
128  }
129}
130
131sub database {
132  my ($self, %options) = @_;
133
134  my $name = $self->_select_name(%options);
135
136  if ( $self->connect_infos->{$name} ) {
137    return $self->connect_infos->{$name}->{database};
138  }
139  else {
140    croak "database $name doesn't exist";
141  }
142}
143
144sub databases {
145  my $self = shift;
146  return sort keys %{ $self->connect_infos };
147}
148
149sub setup_database {
150  my $self = shift;
151
152  my $handle = $self->handle( @_ );
153
154  require Jifty::DBI::SchemaGenerator;
155  my $generator = Jifty::DBI::SchemaGenerator->new( $handle );
156
157  foreach my $schema ( findsubmod $self->schema_base ) {
158    $schema->require or croak "Can't load $schema: $@";
159    $generator->add_model( $schema );
160  }
161  my @statements = $generator->create_table_sql_statements;
162  $handle->begin_transaction;
163  $handle->simple_query( $_ ) foreach @statements;
164  $handle->commit;
165}
166
167sub record {
168  my ($self, $moniker, %options) = @_;
169
170  my $handle = $self->handle( %options );
171
172  my $package = $self->schema_base.'::'.$moniker;
173     $package->require or croak "Can't load $package: $@";
174     $package->new( handle => $handle );
175}
176
177sub collection {
178  my ($self, $moniker, %options) = @_;
179
180  my $handle = $self->handle( %options );
181
182  # XXX: this may be double-edged
183  $moniker .= 'Collection' unless $moniker =~ /Collection$/;
184
185  my $package = $self->schema_base.'::'.$moniker;
186     $package->require;
187  if ($@) {
188    # perhaps you're too lazy to create Collection class.
189    # now we should try creating default one!
190    my $package_body = <<"EOT";
191package $package;
192use strict;
193use base qw( Jifty::DBI::Collection );
1941;
195EOT
196    eval $package_body;
197    croak "Can't load $package: $@" if $@;
198  }
199  $package->new( handle => $handle );
200}
201
202sub begin_transaction { shift->handle( @_ )->begin_transaction }
203sub commit            { shift->handle( @_ )->commit }
204sub rollback          { shift->handle( @_ )->rollback }
205
206sub simple_query      { shift->handle->simple_query( @_ ) }
207sub fetch_result      { shift->handle->fetch_result( @_ ) }
208
2091;
210
211__END__
212
213=head1 NAME
214
215Catalyst::Model::Jifty::DBI - Jifty::DBI Model Class with some magic on top
216
217=head1 SYNOPSIS
218
219In your model class:
220
221  package MyApp::Model:
222  use strict;
223  use base qw( Catalyst::Model::Jifty::DBI );
224  __PACKAGE__->config({
225      schema_base  => 'MyApp::Schema',
226      connect_info => {
227          driver   => 'SQLite',
228          database => 'myapp.db',
229      },
230  });
231  1;
232
233Or you may want to have multiple databases (for partitioning):
234
235  package MyApp::Model:
236  use strict;
237  use base qw( Catalyst::Model::Jifty::DBI );
238  __PACKAGE__->config({
239      schema_base => 'MyApp::Schema',
240      databases   => [
241          {
242              name => 'database1',
243              connect_info => {
244                  driver   => 'SQLite',
245                  database => 'myapp1.db',
246              },
247          },
248          {
249              name => 'database2',
250              connect_info => {
251                  driver   => 'SQLite',
252                  database => 'myapp2.db',
253              },
254          },
255      ],
256  });
257  1;
258
259Then in a controller:
260
261  my $record = $c->model('JDBI::Book');
262     $record->load_by_cols( name => 'foo' );
263
264  my $collection = $c->model('JDBI::BookCollection');
265     $collection->limit( column => 'name', value => 'bar',
266                         operator => 'MATCHES' );
267
268Or, you may want to do more explicitly
269
270  my $record = $c->model('JDBI')->record('Book');
271     $record->load_by_cols( name => 'foo' );
272
273  my $collection = $c->model('JDBI')->collection('BookCollection');
274     $collection->limit( column => 'name', value => 'bar',
275                         operator => 'MATCHES' );
276
277If you want some partitioning:
278
279  my $record_1 = $c->model('JDBI')
280                   ->record('Book', from => 'database1');
281  my $record_2 = $c->model('JDBI')
282                   ->record('Book', from => 'database2');
283
284  my $collection_1 = $c->model('JDBI')
285                       ->collection('BookCollection',
286                                    from => 'database1');
287  my $collection_2 = $c->model('JDBI')
288                       ->collection('BookCollection',
289                                    from => 'database2');
290
291You can also setup a database:
292
293  my $database = $c->model('JDBI')->database;
294  if ( -f $database ) {
295    $c->model('JDBI')->disconnect;
296    unlink $database;
297  }
298  $c->model('JDBI')->setup_database;
299
300You want more? or you don't want any more magic?
301
302  my $handle = $c->model('JDBI')->handle;
303  my $sth = $handle->simple_query( $sql_statement, @binds );
304
305  # Also you can write like this if you use a default handle:
306  my $sth = $c->model('JDBI')
307              ->simple_query( $sql_statement, @binds );
308
309=head1 BACKWARD INCOMPATIBILITY
310
311Current version of Catalyst::Model::Jifty::DBI was once called Catalyst::Model::JDBI::Schemas, which then replaced the original version written by Marcus Ramberg, by the request of Matt S. Trout (Catalyst Core team) to avoid future confusion. I wonder if anyone used the previous one, but note that APIs have been revamped and backward incompatible since 0.03.
312
313=head1 DESCRIPTION
314
315This is a Catalyst model for Jifty::DBI-based schemas, which may or may not be placed under your model class (if you don't want to place them under the model class, pass "schema_base" option to the model).  The model class automatically detect/load your schemas, like Catalyst::Model::DBIC::Schema does.
316
317This model also provides several features for laziness. You don't have to create simple Collection classes (they'll be created on the fly a la Jifty). No more writing schema in other language just to set up databases; C::M::Jifty::DBI takes care of it, on the fly if you want (of course from the perl schemas you prepared; converting raw SQLs to a database to perl schemas is not our way). You may want to use multiple databases of the same schema, or, you may prefer bloody raw SQL statements to complicated object chains. Here you are. Have fun!
318
319=head1 CONFIG
320
321=head2 schema_base
322
323The namespace to look for schema definitions in. All the schemas just below this namespace would be counted.
324
325=head2 connect_info
326
327A hash reference, which would be converted to a hash, then be passed to Jifty::DBI::Handle->new. See L<Jifty::DBI::Handle> for details.
328
329=head2 databases
330
331You may want to use multiple databases (for log rotation, load balancing etc). In this case you can provide multiple "connect_info" hash references under here, as shown in the SYNOPSIS. Actually, above "connect_info" hash reference would be moved in this "databases" array reference internally, with a default name "_" (underscore).
332
333=head1 METHODS
334
335=head2 new
336
337creates a model. Database connection may be or not be prepared, according to the number of connect_info. See above for the configuration.
338
339=head2 record
340
341creates and returns a corresponding (new) Jifty::DBI::Record object. Note that this is just a Record, not a Collection or a RecordSet of DBIC. That means, this object holds one and only single record, and usually you shouldn't reuse this object to let it hold another record. See examples:
342
343  # this works.
344  my $record = $c->model('JDBI')->record('Book');
345     $record->load_by_cols( id => 1 );
346     $record->set_name( 'new name' );  # now inserted/updated
347
348  # this may or may not work as you wish,
349  # depending on what you really want to do.
350  $c->model('JDBI')->record('Book')->load_by_cols( id => 1 );
351  $c->model('JDBI')->record('Book')->set_name( 'new name' );
352
353You can pass an optional hash, as shown in the SYNOPSIS.
354
355  # this tries to fetch a record from a table named 'books'
356  # in a database named 'database'.
357  my $record = $c->model('JDBI')->record('Book', from => 'database');
358
359You can omit "->record" when you fetch from a default database.
360
361  # both do the same thing
362  my $record = $c->model('JDBI')->record('Book');
363  my $record = $c->model('JDBI::Book');
364
365=head2 collection
366
367creates and returns a corresponding (new) Jifty::DBI::Collection object. If you haven't created a Collection class but only a Schema/Record class, this model creates a plain Collection class on the fly. I recommend not to omit the obvious 'Collection' part of the class name, but if you prefer, you can spare that part when you explicitly call model("Model")->collection("Schema") (you can't omit if you follow the model("Model::Schema") convention). Other general usage and caveats are the same as ->record.
368
369  # this works.
370  my $collection = $c->model('JDBI')->collection('BookCollection');
371     $collection->unlimit;
372     $collection->limit( column => 'name', value => 'bar',
373                         operator => 'MATCHES' );
374
375  # this may or may not work as you wish,
376  # depending on what you really want to do.
377  $c->model('JDBI')->collection('BookCollection')->limit;
378  $c->model('JDBI')->collection('BookCollection')->first;
379
380You can pass an optional hash, as shown in the SYNOPSIS.
381
382  # this tries to fetch a collection from a table named 'books'
383  # in a database named 'database'.
384  my $collection = $c->model('JDBI')
385                     ->collection('BookCollection',
386                                  from => 'database');
387
388You can omit "->collection" when you fetch from a default database.
389
390  # both do the same thing
391  my $collection = $c->model('JDBI')->collection('BookCollection');
392  my $collection = $c->model('JDBI::BookCollection');
393
394=head2 simple_query
395
396When you want to do something irrelevant to a specific table, or something too complicated for Jifty::DBI, you can execute arbitrary statements with "simple_query", which is almost equivalent to DBI's $dbh->do or ->prepare. Note that this is supposed to use a default handle. If you want to use other handles, get the handle first with ->handle described below.
397
398  # fetch something from "tables" table,
399  # described in "(Your::Schema::)Table" schema/record class.
400  my $statement = 'select * from tables where id = ?';
401  my $sth = $c->model('JDBI')->simple_query( $statement, 1 );
402  return $sth ? $sth->fetchrow : undef;
403
404  # Above is equivalent to:
405  my $handle = $c->model('JDBI')->handle;
406  my $sth = $handle->simple_query( $statement, 1 );
407  return $sth ? $sth->fetchrow : undef;
408
409=head2 fetch_result
410
411This is a lazier shortcut to realize the example just shown above.
412
413  my $statement = 'select * from tables where id = ?';
414  my $row = $c->model('JDBI')->fetch_result( $statement, 1 );
415
416=head2 handle
417
418C::M::Jifty::DBI may have multiple JDBI handles. You can choose one you want to use like this:
419
420  my $handle = $c->model('JDBI')->handle( name => 'sample.db' );
421
422You can use "from" instead of "name". Also, you can use an alias to the real database name (connect_info->{database}) if you set "name" option in the config.
423
424  my $handle = $c->model('JDBI')->handle( from => 'alias' );
425
426By default, this returns a default handle.
427
428=head2 default_handle_name
429
430returns (or sets) a default handle/database name.
431
432=head2 databases
433
434returns all the database names/aliases registered in the config.
435
436  # See if all the registered SQLite databases have been set up.
437  foreach my $name ( $c->model('JDBI')->databases ) {
438    my $dbfile = $c->model('JDBI')->database( name => $name );
439    warn "database $db does not exist" unless -f $dbfile;
440    warn "database $db is blank" unless -s $dbfile;
441  }
442
443=head2 database
444
445returns a database name (or an actual path to the database for SQLite). See above for an example. You can pass an optional hash to specify database alias explicitly.
446
447  $c->model('JDBI')->database( name => 'alias' );
448
449=head2 setup_database
450
451You can set up database on the fly with your perl schema. You can pass an optional hash to specify target database.
452
453  $c->model('JDBI')->setup_database( name => 'database' );
454
455=head2 begin_transaction
456
457=head2 commit
458
459=head2 rollback
460
461These three are shortcuts to ->handle->(method_name). You can pass an optional hash to specify target database.
462
463=head2 disconnect
464
465This also is a shortcut to ->handle->disconnect. You can pass an optional hash to specify target database.
466
467=head1 SEE ALSO
468
469L<Jifty::DBI>
470
471=head1 AUTHOR
472
473Kenichi Ishigaki, E<lt>ishigaki@cpan.orgE<gt>
474
475original version is written by Marcus Ramberg, E<lt>mramberg@cpan.orgE<gt>
476
477=head1 COPYRIGHT AND LICENSE
478
479Copyright (C) 2007 by Kenichi Ishigaki.
480
481This program is free software; you can redistribute it and/or
482modify it under the same terms as Perl itself.
483
484=cut
Note: See TracBrowser for help on using the browser.