File Coverage

blib/lib/Jifty/DBI/SchemaGenerator.pm
Criterion Covered Total %
statement 82 88 93.1
branch 26 28 92.8
condition 20 24 83.3
subroutine 14 15 93.3
pod 5 5 100.0
total 147 160 91.8


line stmt bran cond sub pod time code
1 1     1   11704 use strict;
  1         1  
  1         26  
2 1     1   3 use warnings;
  1         1  
  1         31  
3              
4             package Jifty::DBI::SchemaGenerator;
5              
6 1     1   3 use base qw(Class::Accessor::Fast);
  1         1  
  1         65  
7 1     1   3 use DBIx::DBSchema;
  1         1  
  1         13  
8 1     1   3 use DBIx::DBSchema::Column;
  1         1  
  1         14  
9 1     1   3 use DBIx::DBSchema::Table;
  1         1  
  1         10  
10 1     1   7 use Class::ReturnValue;
  1         1  
  1         79  
11 1     1   4 use version;
  1         1  
  1         5  
12              
13             our $VERSION = '0.01';
14              
15             # Public accessors
16             __PACKAGE__->mk_accessors(qw(handle));
17              
18             # Internal accessors: do not use from outside class
19             __PACKAGE__->mk_accessors(qw(_db_schema));
20              
21             =head1 NAME
22              
23             Jifty::DBI::SchemaGenerator - Generate a table schema from Jifty::DBI records
24              
25             =head1 DESCRIPTION
26              
27             This module turns a Jifty::Record object into an SQL schema for your chosen
28             database. At the moment, your choices are MySQL, SQLite, or PostgreSQL.
29             Oracle might also work right, though it's untested.
30              
31             =head1 SYNOPSIS
32              
33             =head2 The Short Answer
34              
35             See below for where we get the $handle and $model variables.
36              
37             use Jifty::DBI::SchemaGenerator;
38             ...
39             my $s_gen = Jifty::DBI::SchemaGenerator->new( $handle );
40             $s_gen->add_model($model);
41              
42             my @statements = $s_gen->create_table_sql_statements;
43             print join("\n", @statements, '');
44             ...
45              
46             =head2 The Long Version
47              
48             See L for details about the first two parts.
49              
50             =over
51              
52             =item MyModel
53              
54             package MyModel;
55             # lib/MyModel.pm
56              
57             use warnings;
58             use strict;
59              
60             use base qw(Jifty::DBI::Record);
61             # your custom code goes here.
62             1;
63              
64             =item MyModel::Schema
65              
66             package MyModel::Schema;
67             # lib/MyModel/Schema.pm
68              
69             use warnings;
70             use strict;
71              
72             use Jifty::DBI::Schema;
73              
74             column foo => type is 'text';
75             column bar => type is 'text';
76              
77             1;
78              
79             =item myscript.pl
80              
81             #!/usr/bin/env perl
82             # myscript.pl
83              
84             use strict;
85             use warnings;
86              
87             use Jifty::DBI::SchemaGenerator;
88              
89             use Jifty::DBI::Handle;
90             use MyModel;
91             use MyModel::Schema;
92              
93             my $handle = Jifty::DBI::Handle->new();
94             $handle->connect(
95             driver => 'SQLite',
96             database => 'testdb',
97             );
98              
99             my $model = MyModel->new($handle);
100             my $s_gen = Jifty::DBI::SchemaGenerator->new( $handle );
101             $s_gen->add_model($model);
102              
103             # here's the basic point of this module:
104             my @statements = $s_gen->create_table_sql_statements;
105             print join("\n", @statements, '');
106              
107             # this part is directly from Jifty::Script::Schema::create_all_tables()
108             $handle->begin_transaction;
109             for my $statement (@statements) {
110             my $ret = $handle->simple_query($statement);
111             $ret or die "error creating a table: " . $ret->error_message;
112             }
113             $handle->commit;
114              
115             =back
116              
117             =head1 CONFIGURATION AND ENVIRONMENT
118              
119             Requires no configuration files or environment variables.
120              
121              
122             =head1 DEPENDENCIES
123              
124             Class::Accessor::Fast
125              
126             DBIx::DBSchema
127              
128             Class::ReturnValue
129              
130             =head1 METHODS
131              
132             =head2 new HANDLE
133              
134             Creates a new C object. The single
135             required argument is a C.
136              
137             =cut
138              
139             sub new {
140 11     11 1 2133 my $class = shift;
141 11         12 my $handle = shift;
142 11         34 my $self = $class->SUPER::new();
143              
144 11         88 $self->handle($handle);
145              
146 11         65 my $schema = DBIx::DBSchema->new();
147 11         80 $self->_db_schema($schema);
148              
149 11         83 return $self;
150             }
151              
152             =head2 add_model MODEL
153              
154             Adds a new model class to the SchemaGenerator. Model should be an
155             object which is an instance of C or a subclass
156             thereof. It may also be a string which is the name of such a
157             class/subclass; in the latter case, C will instantiate an
158             object of the class.
159              
160             The model must define the instance methods C and C.
161              
162             Returns true if the model was added successfully; returns a false
163             C error otherwise.
164              
165             =cut
166              
167             sub add_model {
168 14     14 1 2113 my $self = shift;
169 14         11 my $model = shift;
170              
171             # $model could either be a (presumably unfilled) object of a subclass of
172             # Jifty::DBI::Record, or it could be the name of such a subclass.
173              
174 14 100 66     46 unless ( ref $model and UNIVERSAL::isa( $model, 'Jifty::DBI::Record' ) ) {
175 12         7 my $new_model;
176 12         9 eval { $new_model = $model->new; };
  12         45  
177              
178 12 100       15 if ($@) {
179 1         5 return $self->_error("Error making new object from $model: $@");
180             }
181              
182 11 50       28 unless ( UNIVERSAL::isa( $new_model, 'Jifty::DBI::Record' ) ) {
183 0         0 return $self->_error(
184             "Didn't get a Jifty::DBI::Record from $model, got $new_model"
185             );
186             }
187 11         9 $model = $new_model;
188             }
189              
190 13         19 my $table_obj = $self->_db_schema_table_from_model($model);
191              
192 13         23 $self->_db_schema->addtable($table_obj);
193              
194 13         138 return 1;
195             }
196              
197             =head2 column_definition_sql TABLENAME COLUMNNAME
198              
199             Given a table name and a column name, returns the SQL fragment
200             describing that column for the current database.
201              
202             =cut
203              
204             sub column_definition_sql {
205 0     0 1 0 my $self = shift;
206 0         0 my $table = shift;
207 0         0 my $col = shift;
208 0         0 my $table_obj = $self->_db_schema->table($table);
209 0         0 return $table_obj->column( $col )->line( $self->handle->dbh )
210             }
211              
212             =head2 create_table_sql_statements
213              
214             Returns a list of SQL statements (as strings) to create tables for all of
215             the models added to the SchemaGenerator.
216              
217             =cut
218              
219             sub create_table_sql_statements {
220 17     17 1 508 my $self = shift;
221              
222 22         4993 return map { $self->_db_schema->table($_)->sql_create_table($self->handle->dbh) }
  10         39  
223 17         34 sort { $a cmp $b }
224             $self->_db_schema->tables;
225             }
226              
227             =head2 create_table_sql_text
228              
229             Returns a string containing a sequence of SQL statements to create tables for all of
230             the models added to the SchemaGenerator.
231              
232             This is just a trivial wrapper around L.
233              
234             =cut
235              
236             sub create_table_sql_text {
237 16     16 1 6883 my $self = shift;
238              
239 16         33 return join "\n", map {"$_ ;\n"} $self->create_table_sql_statements;
  22         13464  
240             }
241              
242             =head2 PRIVATE _db_schema_table_from_model MODEL
243              
244             Takes an object of a subclass of Jifty::DBI::Record; returns a new
245             C object corresponding to the model.
246              
247             =cut
248              
249             sub _db_schema_table_from_model {
250 13     13   10 my $self = shift;
251 13         11 my $model = shift;
252              
253 13         33 my $table_name = $model->table;
254 13         73 my @columns = $model->columns;
255              
256 13         163 my @cols;
257             my @indexes;
258              
259 13         15 for my $column (@columns) {
260              
261             # Skip "Virtual" columns - (foreign keys to collections)
262 57 100       179 next if $column->virtual;
263              
264             # Skip computed columns
265 51 100       163 next if $column->computed;
266              
267             # If schema_version is defined, make sure columns are for that version
268 50 100 100     229 if ($model->can('schema_version') and defined $model->schema_version) {
269              
270             # Skip it if the app version is earlier than the column version
271 37 100 100     186 next if defined $column->since
272             and $model->schema_version < version->new($column->since);
273              
274             # Skip it if the app version is the same as or later than the
275             # column version
276 36 100 100     219 next if defined $column->till
277             and $model->schema_version >= version->new($column->till);
278              
279             }
280              
281             # Otherwise, assume the latest version and eliminate till columns
282 47 100 100     261 next if (!$model->can('schema_version') or !defined $model->schema_version)
      100        
283             and defined $column->till;
284              
285             # Encode default values
286 45         238 my $default = $column->default;
287              
288             # Scalar::Defer-powered defaults do not get a default in the database
289 45 100 66     182 if (ref($default) ne '0' && defined $default) {
290 6         10 $model->_handle($self->handle);
291 6         17 $model->_apply_input_filters(
292             column => $column,
293             value_ref => \$default,
294             );
295 6 50 33     22 $default = \"''" if defined $default and not length $default;
296 6         11 $model->_handle(undef);
297             } else {
298 39         38 $default = '';
299             }
300              
301 45 100       62 push @cols,
302             DBIx::DBSchema::Column->new(
303             { name => $column->name,
304             type => $column->type,
305             null => $column->mandatory ? 0 : 1,
306             default => $default,
307             }
308             );
309              
310 45 100       620 if ($column->indexed) {
311 1         4 push @indexes,[$column->name];
312             }
313             }
314              
315 13         96 my $index_count = 1;
316 1         7 my $table = DBIx::DBSchema::Table->new(
317             { name => $table_name,
318             primary_key => "id",
319             columns => \@cols,
320 13 100       55 (@indexes) ? (indices => [map {DBIx::DBSchema::Index->new(name => $table_name.$index_count++, columns => $_) } @indexes]) : ()
321             }
322             );
323              
324 13         622 return $table;
325             }
326              
327             =head2 PRIVATE _error STRING
328              
329             Takes in a string and returns it as a Class::ReturnValue error object.
330              
331             =cut
332              
333             sub _error {
334 1     1   1 my $self = shift;
335 1         1 my $message = shift;
336              
337 1         6 my $ret = Class::ReturnValue->new;
338 1         5 $ret->as_error( errno => 1, message => $message );
339 1         498 return $ret->return_value;
340             }
341              
342             1; # Magic true value required at end of module
343              
344             =head1 INCOMPATIBILITIES
345              
346             None reported.
347              
348             =head1 BUGS AND LIMITATIONS
349              
350             No bugs have been reported.
351              
352             Please report any bugs or feature requests to
353             CRT NAMEE@rt.cpan.org>, or through the web interface at
354             L.
355              
356             =head1 AUTHOR
357              
358             David Glasser C<< glasser@bestpractical.com >>
359              
360             Some pod by Eric Wilhelm
361              
362             =head1 LICENCE AND COPYRIGHT
363              
364             Copyright (c) 2005, Best Practical Solutions, LLC. All rights reserved.
365              
366             This module is free software; you can redistribute it and/or
367             modify it under the same terms as Perl itself. See L.
368              
369             =head1 DISCLAIMER OF WARRANTY
370              
371             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
372             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
373             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
374             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
375             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
376             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
377             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
378             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
379             NECESSARY SERVICING, REPAIR, OR CORRECTION.
380              
381             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
382             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
383             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
384             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
385             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
386             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
387             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
388             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
389             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
390             SUCH DAMAGES.
391              
392             =cut
393