File Coverage

blib/lib/DBIx/DataFactory.pm
Criterion Covered Total %
statement 12 12 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 16 16 100.0


line stmt bran cond sub pod time code
1             package DBIx::DataFactory;
2              
3 1     1   67521 use strict;
  1         3  
  1         47  
4 1     1   6 use warnings;
  1         1  
  1         31  
5 1     1   5 use Carp;
  1         6  
  1         109  
6              
7             our $VERSION = '0.0.5';
8              
9 1     1   4 use base qw(Class::Data::Inheritable Class::Accessor::Fast);
  1         2  
  1         2730  
10             __PACKAGE__->mk_classdata('defined_types' => {});
11             __PACKAGE__->mk_accessors(qw(
12             username password dsn dbh connect_attr
13             ));
14              
15             __PACKAGE__->add_type('DBIx::DataFactory::Type::Int');
16             __PACKAGE__->add_type('DBIx::DataFactory::Type::Num');
17             __PACKAGE__->add_type('DBIx::DataFactory::Type::Str');
18             __PACKAGE__->add_type('DBIx::DataFactory::Type::Set');
19              
20             use Smart::Args;
21             use DBIx::Inspector;
22             use DBI;
23             use SQL::Maker;
24             use Sub::Install;
25             use Class::Load qw/load_class/;
26              
27             use DBIx::DataFactory::Type;
28              
29             sub create_factory_method {
30             args my $self,
31             my $method => 'Str',
32             my $table => 'Str',
33             my $dsn => {isa => 'Str', optional => 1},
34             my $username => {isa => 'Str', optional => 1},
35             my $password => {isa => 'Str', optional => 1},
36             my $dbh => {isa => 'Object', optional => 1},
37             my $connect_attr => {
38             isa => 'HashRef', optional => 1,
39             },
40             my $auto_inserted_columns => {
41             isa => 'HashRef', optional => 1, default => {},
42             },
43             my $install_package => {
44             isa => 'Str', optional => 1, default => 'DBIx::DataFactory',
45             },
46             my $creator => {
47             isa => 'CodeRef', optional => 1,
48             };
49              
50             $username = $self->username unless $username;
51             $password = $self->password unless $password;
52             $dsn = $self->dsn unless $dsn;
53             $dbh = $self->dbh unless $dbh;
54             unless ((defined $username && defined $password && defined $dsn) || $dbh) {
55             croak('either username, password and dsn for database, or dbh are required');
56             }
57              
58             $connect_attr = $self->connect_attr || {} unless $connect_attr;
59             $dbh ||= DBI->connect($dsn, $username, $password, $connect_attr);
60             my $inspector = DBIx::Inspector->new(dbh => $dbh)
61             or croak('cannot connect database');
62              
63             my ($inspector_table) = grep {$_->name eq $table} $inspector->tables;
64             croak("cannot find table named $table") unless $inspector_table;
65              
66             my $table_columns = [map {$_->name} $inspector_table->columns];
67             my $primary_keys = [map {$_->name} $inspector_table->primary_key];
68              
69             my $builder = SQL::Maker->new(driver => $dbh->{Driver}->{Name});
70              
71             Sub::Install::install_sub({
72             code => sub {
73             my ($class, %args) = @_;
74             return $self->_factory_method(
75             dbh => $dbh,
76             table => $table,
77             column_names => $table_columns,
78             builder => $builder,
79             primary_keys => $primary_keys,
80             auto_inserted_columns => $auto_inserted_columns,
81             creator => $creator,
82             params => \%args,
83             );
84             },
85             into => $install_package,
86             as => $method,
87             });
88              
89             return;
90             }
91              
92             sub add_type {
93             my ($class, $type) = @_;
94             load_class($type);
95             $class->defined_types->{$type->type_name} = $type;
96             }
97              
98             sub _make_value_from_type_info {
99             my ($class, $args) = @_;
100              
101             my $copy_arg = {};
102             %$copy_arg = %$args;
103             my $type_name = delete $copy_arg->{type};
104             my $type_class = $class->defined_types->{$type_name}
105             or croak("$type_name is not defined as type");
106              
107             return $type_class->make_value(%$copy_arg);
108             }
109              
110             sub _factory_method {
111             my ($self, %args) = @_;
112             my $dbh = $args{dbh};
113             my $table = $args{table};
114             my $columns = $args{column_names};
115             my $params = $args{params};
116             my $builder = $args{builder};
117             my $pk = $args{primary_keys};
118             my $creator = $args{creator};
119             my $auto_inserted_columns = $args{auto_inserted_columns};
120              
121             my $values = {};
122             for my $column (@$columns) {
123             # insert specified value if specified
124             my $specified = $params->{$column};
125             if (defined $specified) {
126             $values->{$column} = $specified;
127             next;
128             }
129              
130             # insert setting columns value
131             my $default = $auto_inserted_columns->{$column};
132             if (ref $default eq 'CODE') {
133             $values->{$column} = $default->();
134             next;
135             }
136             elsif (ref $default eq 'HASH') {
137             my $value = DBIx::DataFactory->_make_value_from_type_info($default);
138             $values->{$column} = $value;
139             next;
140             }
141             }
142              
143             if ($creator) {
144             return $creator->($values);
145             }
146             else {
147             return $self->_insert(
148             builder => $builder,
149             dbh => $dbh,
150             table => $table,
151             primary_keys => $pk,
152             values => $values,
153             );
154             }
155             }
156              
157             sub _insert {
158             my ($self, %args) = @_;
159             my $builder = $args{builder};
160             my $dbh = $args{dbh};
161             my $table = $args{table};
162             my $pk = $args{primary_keys};
163             my $values = $args{values};
164              
165             # make sql for insert
166             my ($sql, @binds) = $builder->insert($table, $values);
167              
168             # insert
169             my $sth = $dbh->prepare($sql);
170             $sth->execute(@binds);
171              
172             # set auto increment value
173             if (scalar(@$pk) == 1 && not defined $values->{$pk->[0]}) {
174             $values->{$pk->[0]} = $self->_last_insert_id(
175             $dbh, $builder->{driver}, $table,
176             );
177             }
178              
179             # refetch data
180             if (scalar(@$pk) == 1) {
181             my ($sql, @binds) = $builder->select(
182             $table,
183             ['*'],
184             { $pk->[0] => $values->{$pk->[0]} },
185             );
186             my $row_hash = $dbh->selectrow_hashref($sql, {}, @binds);
187             return $row_hash if $row_hash;
188             }
189              
190             return $values;
191             }
192              
193             sub _last_insert_id {
194             my ($self, $dbh, $driver, $table_name) = @_;
195              
196             if ( $driver eq 'mysql' ) {
197             return $dbh->{mysql_insertid};
198             } elsif ( $driver eq 'Pg' ) {
199             return $dbh->last_insert_id( undef, undef, undef, undef,{ sequence => join( '_', $table_name, 'id', 'seq' ) } );
200             } elsif ( $driver eq 'SQLite' ) {
201             return $dbh->func('last_insert_rowid');
202             } elsif ( $driver eq 'Oracle' ) {
203             return;
204             } else {
205             Carp::croak "Don't know how to get last insert id for $driver";
206             }
207             }
208              
209             1;
210              
211             __END__
212              
213             =head1 NAME
214              
215             DBIx::DataFactory - factory method maker for inserting test data
216              
217             =head1 SYNOPSIS
218              
219             # schema
220             CREATE TABLE test_factory (
221             `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
222             `int` int,
223             `double` double,
224             `string` varchar(255),
225             `text` text DEFAULT NULL,
226              
227             PRIMARY KEY (id)
228             ) DEFAULT CHARSET=binary;
229              
230             # in your t/*.t
231             use DBIx::DataFactory;
232             my $factory_maker = DBIx::DataFactory->new({
233             username => 'nobody',
234             password => 'nobody',
235             dsn => 'dbi:mysql:dbname=test_factory;host=localhost',
236             });
237             $factory_maker->create_factory_method(
238             method => 'create_factory_data',
239             table => 'test_factory',
240             auto_inserted_columns => {
241             int => {
242             type => 'Int',
243             size => 8,
244             },
245             double => sub { rand(100) },
246             string => {
247             type => 'Str',
248             size => 10,
249             },
250             },
251             );
252              
253             my $values = $factory_maker->create_factory_data(
254             text => 'test text',
255             );
256             # or you can use DBIx::DataFactory->create_factory_data()
257             my $int = $values->{int};
258             my $text = $values->{text};
259              
260             # will insert following data
261             # +----+----------+------------------+------------+-----------+
262             # | id | int | double | string | text |
263             # +----+----------+------------------+------------+-----------+
264             # | 1 | 60194256 | 3.03977754238112 | fHt4X0JDr9 | test text |
265             # +----+----------+------------------+------------+-----------+
266              
267             $values = $factory_maker->create_factory_data(
268             int => 1,
269             string => 'test',
270             );
271              
272             # will insert following data
273             # +----+------+-----------------+--------+------+
274             # | id | int | double | string | text |
275             # +----+------+-----------------+--------+------+
276             # | 2 | 1 | 71.159467713824 | test | NULL |
277             # +----+------+-----------------+--------+------+
278              
279             =head1 DESCRIPTION
280              
281             This module helps you to make factory method for inserting data into database. You can use this as fixture replacement.
282              
283             =head1 METHODS
284              
285             =head2 $class->new(%args)
286              
287             Create a new DBIx::DataFactory object.
288              
289             # set up by username, password, and dsn
290             my $factory_maker = DBIx::DataFactory->new({
291             username => 'nobody',
292             password => 'nobody',
293             dsn => 'dbi:mysql:dbname=test_factory;host=localhost',
294             });
295              
296             # or set up by db handler
297             my $dbh = DBI->connect(
298             'dbi:mysql:dbname=test_factory;host=localhost',
299             'nobody',
300             'nobody',
301             );
302             my $factory_maker = DBIx::DataFactory->new({
303             dbh => $dbh,
304             });
305              
306              
307             Set up initial state by following parameters.
308              
309             =over 4
310              
311             =item * username
312              
313             Database username.
314              
315             =item * password
316              
317             Database password
318              
319             =item * dsn
320              
321             Database dsn
322              
323             =item * dbh
324              
325             Database handler
326              
327             =back
328              
329             =head2 $self->create_factory_method(%args)
330              
331             This installs the method, which helps inserting data into database, in the DBIx::DataFactory package by default.
332              
333             $factory_maker->create_factory_method(
334             method => 'create_factory_data',
335             table => 'test_factory',
336             auto_inserted_columns => {
337             int => {
338             type => 'Int',
339             size => 8,
340             },
341             string => {
342             type => 'Str',
343             size => 10,
344             },
345             },
346             );
347              
348             if this is the case, this make the method named 'create_factory_data'. you can pass all columns value you defined in schema.
349              
350             my $values = $factory_maker->create_factory_data(
351             int => 5,
352             string => 'string',
353             text => 'test text',
354             );
355              
356             # this makes following data.
357             +----+-----+--------+-----------+
358             | id | int | string | text |
359             +----+-----+--------+-----------+
360             | 1 | 5 | string | test text |
361             +----+-----+--------+-----------+
362              
363              
364             my $values = $factory_maker->create_factory_data;
365              
366             # this makes following data
367             +----+----------+------------+------+
368             | id | int | string | text |
369             +----+----------+------------+------+
370             | 2 | 59483011 | 9svzODgYyz | NULL |
371             +----+----------+------------+------+
372              
373             =head3 Parameters
374              
375             =over 4
376              
377             =item * method
378              
379             Required parameter. method name you want to create.
380              
381             =item * table
382              
383             Required parameter. database table name.
384              
385             =item * dsn
386              
387             optional parameter. database dsn.
388              
389             =item * username
390              
391             optional parameter. database username.
392              
393             =item * password
394              
395             optional parameter. database password.
396              
397             =item * auto_inserted_columns
398              
399             optional parameter. if you have the table column which you want to insert data into automatically by default, you can specify this parameter.
400              
401             for example, if you have columns named 'int', 'string', and 'text', you can specify following.
402              
403             $factory_maker->create_factory_method(
404             method => 'create_factory_data',
405             table => 'test_factory',
406             auto_inserted_columns => {
407             int => {
408             type => 'Int',
409             size => 8,
410             },
411             string => {
412             type => 'Str',
413             size => 10,
414             },
415             text => sub { String::Random->new->randregex('[a-z]{50}') }
416             },
417             );
418              
419             if passed hashref, the method inserts data which is defined in specified type class automatically by default. see also DBIx::DataFactory::Type.
420              
421             if passed coderef, the method inserts value which the code returns.
422              
423             Of cource, if you specify column value in installed method, the setting for the column is not used.
424              
425             =item * install_package
426              
427             optional parameter. if you want to install the factory method to package except DBIx::DataFactory, please specify.
428              
429             $factory_maker->create_factory_method(
430             method => 'create_factory_data',
431             table => 'test_factory',
432             install_package => 'test::DBIx::DataFactory',
433             );
434              
435             =item * creator
436              
437             optional parameter. if you want to use original method for creating data, please specify coderef.
438              
439             DBIx::DataFactory passes values for inserting to code. the method created by create_factory_method returns values which passed coderef returns.
440              
441             this is probably useful when you use ORM and set up trigger, or when you want to use blessed value as return value.
442              
443             For example,
444              
445             $factory_maker->create_factory_method(
446             method => 'create_factory_data',
447             table => 'test_factory',
448             creator => sub {
449             my ($values) = @_;
450              
451             my $db = DBIx::Simple->connect(
452             'dbi:mysql:test_factory', 'root', '',
453             ); # your setting
454             $db->abstract = SQL::Abstract->new;
455              
456             my $result = $db->insert('test_factory', $values);
457             return $result; # this is used for return value of create_factory_data
458             },
459             );
460              
461             =back
462              
463             =head2 add_type
464              
465             you can add type class which define the rule of inserting data. See also DBIx::DataFactory::Type.
466              
467             DBIx::DataFactory->add_type('DBIx::DataFactory::Type::Test');
468              
469             =head1 REPOSITORY
470              
471             https://github.com/shibayu36/p5-DBIx-DataFactory
472              
473             =head1 AUTHOR
474              
475             C<< <shibayu36 {at} gmail.com> >>
476              
477             =head1 LICENCE AND COPYRIGHT
478              
479             Copyright (c) 2011, Yuki Shibazaki C<< <shibayu36 {at} gmail.com> >>. All rights reserved.
480              
481             This module is free software; you can redistribute it and/or
482             modify it under the same terms as Perl itself. See L<perlartistic>.