File Coverage

blib/lib/DBIx/Class/EasyFixture.pm
Criterion Covered Total %
statement 93 95 97.8
branch 22 26 84.6
condition 8 9 88.8
subroutine 19 21 90.4
pod 5 8 62.5
total 147 159 92.4


line stmt bran cond sub pod time code
1             package DBIx::Class::EasyFixture;
2             $DBIx::Class::EasyFixture::VERSION = '0.13';
3             # ABSTRACT: Easy fixtures with DBIx::Class
4              
5 10     10   7642850 use 5.008003;
  10         102  
6 10     10   615 use Moo;
  10         11224  
  10         66  
7 10     10   9332 use MooX::HandlesVia;
  10         16886  
  10         61  
8 10     10   6752 use Types::Standard qw(InstanceOf Bool HashRef);
  10         777602  
  10         120  
9 10     10   10934 use Carp;
  10         25  
  10         693  
10 10     10   4878 use aliased 'DBIx::Class::EasyFixture::Definition';
  10         6920  
  10         63  
11 10     10   1122 use namespace::autoclean;
  10         24  
  10         94  
12              
13             has 'schema' => (
14             is => 'ro',
15             isa => InstanceOf['DBIx::Class::Schema'],
16             required => 1,
17             );
18             has '_in_transaction' => (
19             is => 'rw',
20             isa => Bool,
21             default => 0,
22             writer => '_set_in_transaction',
23             );
24             has '_cache' => (
25             is => 'ro',
26             isa => HashRef,
27             default => sub { {} },
28             handles_via => 'Hash',
29             handles => {
30             _set_fixture => 'set',
31             _get_result => 'get',
32             _clear => 'clear',
33             is_loaded => 'exists',
34             },
35             );
36             has 'no_transactions' => (
37             is => 'ro',
38             isa => Bool,
39             default => 0,
40             );
41              
42             sub BUILD {
43 13     13 0 1349 my $self = shift;
44              
45             # Creating a definition object validates them, so this tells us at
46             # construction time if all fixtures are valid.
47 13         79 $self->_get_definition_object($_) foreach $self->all_fixture_names;
48             }
49              
50             sub load {
51 17     17 1 28736 my ( $self, @fixtures ) = @_;
52 17 100 100     503 if ( not $self->no_transactions and not $self->_in_transaction ) {
53 13         278 $self->schema->txn_begin;
54 13         281666 $self->_set_in_transaction(1);
55             }
56 17         453 my @dbic_objects;
57 17         52 foreach my $fixture (@fixtures) {
58 46         1540 my $definition = $self->_get_definition_object($fixture);
59 46 100       439 if ( my $group = $definition->group ) {
60 2         16 push @dbic_objects => $self->load(@$group);
61             }
62             else {
63 44         181 push @dbic_objects => $self->_load($definition);
64             }
65             }
66 17 100 66     446 return 1 if not defined wantarray or not @dbic_objects;
67 16 100       147 return $dbic_objects[0] if not wantarray;
68 3         21 return @dbic_objects;
69             }
70              
71             sub _get_definition_object {
72 424     424   2034 my ( $self, $fixture ) = @_;
73             return Definition->new(
74             { name => $fixture,
75             definition => $self->get_definition($fixture),
76 424         1473 fixtures => { map { $_ => 1 } $self->all_fixture_names },
  9752         28608  
77             }
78             );
79             }
80              
81             sub get_result {
82 4     4 1 9046 my ( $self, $fixture ) = @_;
83              
84 4 100       104 unless ( $self->is_loaded($fixture) ) {
85 1         94 carp("Fixture '$fixture' was never loaded");
86 1         1434 return;
87             }
88 3         198 return $self->_get_result($fixture);
89             }
90              
91             sub _get_object {
92 78     78   222 my ( $self, $definition ) = @_;
93              
94 78         213 my $name = $definition->name;
95 78         1572 my $object = $self->_get_result($name);
96 78 100       7640 unless ($object) {
97 70         251 my $args = $definition->constructor_data;
98 70 50       193 if ( my $requires = $definition->requires_pre ) {
99 70         281 while ( my ( $parent, $methods ) = each %$requires ) {
100 35 50       734 my $other = $self->_get_result($parent)
101             or croak("Panic: required object '$parent' not loaded");
102 35         3329 my ( $our, $their ) = @{$methods}{qw/our their/};
  35         119  
103 35         766 $args->{$our} = $other->$their;
104             }
105             }
106 70         940 $object = $self->schema->resultset( $definition->resultset_class )
107             ->create( $definition->constructor_data );
108 70         972254 $self->_set_fixture( $name, $object );
109             }
110 78         12571 return $object;
111             }
112              
113             sub _load {
114 123     123   294 my ( $self, $definition ) = @_;
115              
116             # XXX This is our guard against circular definitions.
117 123 100       2477 if ( $self->is_loaded( $definition->name ) ) {
118 45         2941 return $self->_get_result( $definition->name );
119             }
120              
121 78 50       3990 if ( my $requires = $definition->requires_pre ) {
122 78         250 $self->_load_previous_fixtures($requires);
123             }
124              
125 78         2333 my $object = $self->_get_object($definition);
126              
127 78 100       350 if ( my $next = $definition->next ) {
128 27         163 $self->_load_next_fixtures($next);
129             }
130              
131 78 50       1170 if ( my $deferred = $definition->requires_defer ) {
132 78         251 $self->_load_deferred_fixtures( $object, $deferred );
133             }
134 78         20241 return $object;
135             }
136              
137             sub _load_previous_fixtures {
138 78     78   181 my ( $self, $requires ) = @_;
139              
140 78         256 foreach my $parent ( keys %$requires ) {
141 45         872 $self->_load( $self->_get_definition_object($parent) );
142             }
143             }
144              
145             sub _load_next_fixtures {
146 27     27   75 my ( $self, $next ) = @_;
147              
148 27         76 foreach my $fixture (@$next) {
149 30         83 my $definition = $self->_get_definition_object($fixture);
150              
151             # XXX This is done when _load calls _load_previous_fixtures,
152             # so it is not necessary here
153             #
154             # # a fixture may say "load these other fixtures next", but if that
155             # # happens, those "next" fixtures may have different parent fixtures
156             # # that they require, so we load them here.
157             # if ( my $requires = $definition->requires_pre ) {
158             # while ( my ( $parent, $methods ) = each %$requires ) {
159             # $self->_load( $self->_get_definition_object($parent) )
160             # || croak("Panic: related object '$parent' not loaded");
161             # }
162             # }
163              
164             # ... and *now* we can load the fixture
165 30         238 $self->_load($definition);
166             }
167             }
168              
169             sub _load_deferred_fixtures {
170 78     78   173 my ( $self, $object, $deferred) = @_;
171              
172 78         134 my $update_args = {};
173 78         133 while( my ($fixture, $methods) = each( %{$deferred} ) ) {
  82         380  
174 4         11 my $other = $self->_load( $self->_get_definition_object($fixture) );
175 4         252 my ( $our, $their ) = @{$methods}{qw/our their/};
  4         17  
176 4         117 $update_args->{$our} = $other->$their;
177             }
178              
179 78         492 $object->update( $update_args );
180             }
181              
182             sub unload {
183 16     16 1 38452 my $self = shift;
184 16 100 100     403 if ( not $self->no_transactions and $self->_in_transaction ) {
185 13         282 $self->schema->txn_rollback;
186 13         7815 $self->_clear;
187 13         3937 $self->_set_in_transaction(0);
188             }
189             else {
190             # XXX I don't think I really need this
191             #carp("finish() called without load()");
192             }
193 16         654 return 1;
194             }
195              
196             sub all_fixture_names {
197 0     0 1 0 croak("You must override all_fixture_names() in a subclass");
198             }
199              
200             sub get_definition {
201 0     0 1 0 croak("You must override get_definition() in a subclass");
202             }
203              
204 7     7 0 1141 sub fixture_loaded { $_[0]->is_loaded( $_[1] ) }
205              
206             sub DEMOLISH {
207 13     13 0 204356 my $self = shift;
208 13         119 $self->unload;
209             }
210              
211             1;
212              
213             __END__
214              
215             =pod
216              
217             =encoding UTF-8
218              
219             =head1 NAME
220              
221             DBIx::Class::EasyFixture - Easy fixtures with DBIx::Class
222              
223             =head1 VERSION
224              
225             version 0.13
226              
227             =head1 SYNOPSIS
228              
229             package My::Fixtures;
230             use Moo; # (Moose is also fine)
231             extends 'DBIx::Class::EasyFixture';
232              
233             sub get_definition { ... }
234             sub all_fixture_names { ... }
235              
236             And in your test code:
237              
238             my $fixtures = My::Fixtures->new( { schema => $schema } );
239             my $dbic_object = $fixtures->load('some_fixture');
240              
241             # run your tests
242              
243             $fixtures->unload;
244              
245             Note that C<unload> will be called for you if your fixture object falls out of
246             scope.
247              
248             =head1 DESCRIPTION
249              
250             The latest version of this is always at
251             L<https://github.com/Ovid/dbix-class-easyfixture>.
252              
253             This is C<ALPHA> code. Documentation is on its way, including a tutorial. For
254             now, you'll have to read the tests. You can read F<t/lib/My/Fixtures.pm> to
255             see how fixtures are defined.
256              
257             I wanted an easier way to load fixtures for L<DBIx::Class> code. I looked at
258             L<DBIx::Class::Fixtures> and it made a lot of assumptions that, while
259             appropriate for some, is not what I wanted (such as the necessity of storing
260             fixtures in JSON files), and had a reliance on knowing the values of primary
261             keys, I wrote this to make it easier to define and load L<DBIx::Class>
262             fixtures for tests.
263              
264             =head1 METHODS
265              
266             =head2 C<new>
267              
268             my $fixtures = Subclass::Of::DBIx::Class::EasyFixture->new({
269             schema => $dbix_class_schema_instance,
270             });
271              
272             This creates and returns a new instance of your C<DBIx::Class::EasyFixture>
273             subclass. All fixture definitions are validated at this time and the
274             constructor will C<croak()> with a useful error message upon validation
275             failure.
276              
277             =head2 C<all_fixture_names>
278              
279             my @fixture_names = $fixtures->all_fixture_names;
280              
281             Must overridden in your subclass. Should return a list (not an array ref!) of
282             all fixture names available. This is used internally to generate error
283             messages if a fixture attempts to reference a non-existent fixture in its
284             C<next> or C<requires> section.
285              
286             =head2 C<get_definition>
287              
288             my $definition = $fixtures->get_definition($fixture_name);
289              
290             Must be overridden in a subclass. Should return the fixture definition for the
291             fixture name passed in. Should return C<undef> if the fixture is not found.
292              
293             =head2 C<get_result>
294              
295             my $dbic_result_object = $fixtures->get_result($fixture_name);
296              
297             Returns the C<DBIx::Class::Result> object for the given fixture name. Will
298             C<carp> if the fixture wasn't loaded (this may become a fatal error in future
299             versions).
300              
301             =head2 C<load>
302              
303             my @dbic_objects = $fixtures->load(@list_of_fixture_names);
304              
305             Attempts to load all fixtures passed to it. If a transaction has not already
306             been started, it will be started now. This method may be called multiple
307             times and it returns the fixtures loaded. If called in scalar context, only
308             returns the first fixture loaded.
309              
310             =head2 C<unload>
311              
312             $fixtures->unload;
313              
314             Rolls back the transaction started with C<load>
315              
316             =head2 C<is_loaded>
317              
318             if ( $fixtures->is_loaded($fixture_name) ) {
319             ...
320             }
321              
322             Returns a boolean value indicating whether or not the given fixture was
323             loaded.
324              
325             *Note*: Originally this method was called C<fixture_loaded>. That was a bad
326             name. However, C<fixture_loaded> still works as an alias to C<is_loaded>.
327              
328             =head1 TRANSACTIONS
329              
330             If you attempt to load a fixture, a transaction is started and it will be
331             rolled back when you call C<unload()> or when the fixture object falls out of
332             scope. If, for some reason, you do not want transactions (for example, if you
333             need to control them manually), you can use a true value with the
334             C<no_transactions> argument.
335              
336             my $fixtures = My::Fixtures->new(
337             schema => $schema,
338             no_transactions => 1,
339             );
340              
341             =head1 FIXTURES
342              
343             If the following is unclear, see L<DBIx::Class::EasyFixture::Tutorial>.
344              
345             The C<get_definition($fixture_name)> method must always return a fixture
346             definition. The definition must be either a fixture group or a fixture
347             builder.
348              
349             A fixture group is an array reference containing a list of fixture names. For
350             example, C<< $fixture->get_definition('all_people') >> might return:
351              
352             [qw/ person_1 person_2 person_2 /]
353              
354             A fixture builder must return a hash reference with the one or more of the
355             following keys:
356              
357             =over 4
358              
359             =item * C<new> (required)
360              
361             A C<DBIx::Class> result source name.
362              
363             {
364             new => 'Person',
365             using => {
366             name => 'Bob',
367             email => 'bob@example.com',
368             }
369             }
370              
371             Internally, the above will do something similar to this:
372              
373             $schema->resultset($definition->{name})
374             ->create($definition->{using});
375              
376             =item * C<using> (required)
377              
378             A hashref of key/value pairs that will be used to create the C<DBIx::Class>
379             result source referred to by the C<new> key.
380              
381             {
382             new => 'Person',
383             using => {
384             name => 'Bob',
385             email => 'bob@example.com',
386             }
387             }
388              
389             =item * C<next> (optional)
390              
391             If present, this must point to an array reference of fixture names (in other
392             words, a fixture group). These fixtures will then be built I<after> the
393             current fixture is built.
394              
395             {
396             new => 'Person',
397             using => {
398             name => 'Bob',
399             email => 'bob@example.com',
400             },
401             next => [@list_of_fixture_names],
402             }
403              
404             =item * C<requires> (optional)
405              
406             Must point to either a scalar of an attribute name or a hash mapping of
407             attribute names.
408              
409             Many fixtures require data from another fixture. For example, a customer might
410             require a person object being built and the following won't work:
411              
412             {
413             new => 'Customer',
414             using => {
415             first_purchase => $datetime_object,
416             person_id => 'some_person.person_id',
417             }
418             }
419              
420             Assuming we already have a C<Person> fixture defined and it's named
421             C<some_person> and its ID is named C<id>, we can do this:
422              
423             {
424             new => 'Customer',
425             using => { first_purchase => $datetime_object },
426             requires => {
427             some_person => {
428             our => 'person_id',
429             their => 'id',
430             },
431             },
432             }
433              
434             If you prefer, you can I<inline> the C<requires> into the C<using> key. You
435             may find this syntax cleaner:
436              
437             {
438             new => 'Customer',
439             using => {
440             first_purchase => $datetime_object,
441             person_id => { some_person => 'id' },
442             },
443             }
444              
445             The C<our> key refers to the attribute for the C<Customer> fixture and the
446             C<their> key refers to the attribute of the C<Person> fixture. As a
447             convenience, if both attributes have the same name, you can omit that hashref
448             and just use the attribute name:
449              
450             {
451             new => 'Customer',
452             using => { first_purchase => $datetime_object },
453             requires => {
454             some_person => 'person_id',
455             },
456             }
457              
458             And multiple C<requires> can be specified:
459              
460             {
461             new => 'Customer',
462             using => { first_purchase => $datetime_object },
463             requires => {
464             some_person => 'person_id',
465             primary_contact => 'contact_id',
466             },
467             }
468              
469             Or you can skip the C<requires> block entirely and write the above like this
470             (which is now the preferred syntax, but whatever floats your boat):
471              
472             {
473             new => 'Customer',
474             using => {
475             first_purchase => $datetime_object,
476             person_id => { some_person => 'person_id' },
477             contact_id => { primary_contact => 'contact_id' },
478             },
479             }
480              
481             If both the current fixture and the other fixture it requires have the same
482             name for the attribute, a reference to the other fixture name (scalar
483             reference) will suffice:
484              
485             {
486             new => 'Customer',
487             using => {
488             first_purchase => $datetime_object,
489             person_id => \'some_person',
490             contact_id => \'primary_contact',
491             },
492             }
493             The above will construct the fixture like this:
494              
495             $schema->resultset('Customer')->create({
496             first_purchase => $datetime_object,
497             person_id => $person->person_id,
498             primary_contact => $contact->contact_id,
499             });
500              
501             =back
502              
503             When writing a fixture builder, remember that C<requires> are always built
504             before the current fixture and C<next> is also built after the current
505             fixture.
506              
507             =head1 TUTORIAL
508              
509             See L<DBIx::Class::EasyFixture::Tutorial>.
510              
511             =head1 AUTHOR
512              
513             Curtis "Ovid" Poe, C<< <ovid at cpan.org> >>
514              
515             =head1 TODO
516              
517             =over 4
518              
519             =item * Prevent circular fixtures
520              
521             Currently it's very easy to define circular dependencies. We'll worry about
522             that later when it becomes more clear how to best handle them.
523              
524             =item * Better load information
525              
526             Track what fixtures are requested and what fixtures are loaded (and in which
527             order). This makes for better error reporting.
528              
529             =back
530              
531             =head1 AUTHOR
532              
533             Curtis "Ovid" Poe <ovid@cpan.org>
534              
535             =head1 COPYRIGHT AND LICENSE
536              
537             This software is copyright (c) 2014 by Curtis "Ovid" Poe.
538              
539             This is free software; you can redistribute it and/or modify it under
540             the same terms as the Perl 5 programming language system itself.
541              
542             =cut