File Coverage

blib/lib/Pinwheel/Fixtures.pm
Criterion Covered Total %
statement 136 137 99.2
branch 24 24 100.0
condition 12 12 100.0
subroutine 22 22 100.0
pod 5 5 100.0
total 199 200 99.5


line stmt bran cond sub pod time code
1             package Pinwheel::Fixtures;
2              
3 2     2   28027 use strict;
  2         5  
  2         73  
4 2     2   10 use warnings;
  2         4  
  2         55  
5              
6 2     2   2569 use FindBin qw($Bin);
  2         3752  
  2         309  
7 2     2   3279 use File::Slurp;
  2         40042  
  2         431  
8 2     2   27 use POSIX qw(strftime);
  2         6  
  2         20  
9 2     2   5725 use YAML::Tiny;
  2         19455  
  2         165  
10              
11 2     2   5599 use Pinwheel::Context;
  2         17  
  2         124  
12 2     2   607 use Pinwheel::Database qw(without_foreign_keys);
  2         6  
  2         271  
13 2     2   6190 use Pinwheel::View::ERB;
  2         9  
  2         3739  
14              
15             our @ISA = qw(Exporter);
16             our @EXPORT = qw(fixtures scenario identify);
17             our @EXPORT_OK = qw(insert_fixtures empty_tables);
18              
19              
20             our $fixtures_path = "$Bin/../fixtures";
21             our $last_caller = '';
22             our $helpers;
23             my %ids;
24              
25              
26             sub fixtures
27             {
28 6     6 1 19307 my (@names) = @_;
29 6         29 my ($caller) = caller();
30              
31             without_foreign_keys {
32 6 100   6   62 if ($caller ne $last_caller) {
33 1         4 empty_tables();
34 1         7 $last_caller = $caller;
35             }
36 6         21 foreach my $table (@names) {
37 6         48 insert_fixtures(_load_yaml("$fixtures_path/$table.yml"), $table);
38             }
39 6         64 };
40              
41             # For doctest niceness, otherwise the result is that of the commit
42 6         253 return;
43             }
44              
45             # See http://code.google.com/p/fixture-scenarios/
46             sub scenario
47             {
48 5     5 1 9750 my ($name, %opts) = @_;
49 5         13 my (@dirs, $path);
50              
51 5         19 $last_caller = caller();
52 5         14 $path = $fixtures_path;
53 5 100 100     46 if (!exists($opts{'root'}) || $opts{'root'}) {
54 3         7 push @dirs, $path;
55             }
56 5         26 foreach (split('/', $name)) {
57 8         25 $path .= '/' . $_;
58 8         18 push @dirs, $path;
59             }
60              
61             without_foreign_keys {
62 5     5   21 empty_tables();
63 5         3078 foreach $path (@dirs) {
64 11         6033 foreach (glob("$path/*.yml")) {
65 20         526 /\/([^\/]+)\.yml$/;
66 20         258 insert_fixtures(_load_yaml($_), $1);
67             }
68             }
69 5         62 };
70              
71             # For doctest niceness, otherwise the result is that of the commit
72 5         100 return;
73             }
74              
75             sub empty_tables
76             {
77 6     6 1 28 foreach my $table (Pinwheel::Database::tables()) {
78 24         344 my $sth = Pinwheel::Database::prepare("DELETE FROM $table");
79 24         975692 $sth->execute();
80             }
81             }
82              
83             sub insert_fixtures
84             {
85 26     26 1 27135 my ($fixtures, $table) = @_;
86 26         52 my ($sth, $info, %defaults, @keys);
87 0         0 my ($label, $row, @fields, $columns, $values);
88              
89 26         144 $info = Pinwheel::Database::describe($table);
90 26         164 foreach (keys %$info) {
91 81 100 100     1918 if ($_ =~ /^(?:cre|upd)ated_(?:at|on)$/) {
    100          
92             # created_at/on and updated_at/on default to the current time
93 8         609 $defaults{$_} = strftime('%Y-%m-%d %H:%M:%S', gmtime());
94             } elsif ($_ =~ /_id$/ && $info->{$_}{type} =~ /^int\b/) {
95             # Foreign keys can be supplied as labels
96 17         103 push @keys, $_;
97             }
98             }
99              
100 26         74 $sth = {};
101 26         164 while (($label, $row) = each(%$fixtures)) {
102 74         1141 $row = {%defaults, %$row};
103              
104             # If no id, generate one by hashing the label
105 74 100 100     1029 if (exists($info->{id}) && !exists($row->{id})) {
106 12         143 $row->{id} = identify($label);
107             }
108             # Convert foreign keys supplied as labels
109 74         605 foreach (@keys) {
110 53 100 100     1305 if ($row->{$_} && $row->{$_} =~ /[^0-9]/) {
111 8         34 $row->{$_} = identify($row->{$_});
112             }
113             }
114              
115 74         773 @fields = keys %$row;
116 74         1805 $columns = join(', ', map { "`$_`" } @fields);
  233         2790  
117 74 100       455 unless ($sth->{$columns}) {
118 57         266 $values = join(', ', ('?') x scalar(@fields));
119 57         1037 $sth->{$columns} = Pinwheel::Database::prepare(
120             "REPLACE INTO $table ($columns) VALUES ($values)"
121             );
122             }
123 74         307 $sth->{$columns}->execute(@{$row}{@fields});
  74         3227250  
124             }
125             }
126              
127             sub _load_yaml
128             {
129 26     26   92 my ($filename) = @_;
130 26         68 my ($data, $tmpl);
131              
132 26         243 $data = read_file($filename, binmode => ':raw');
133 26 100       8690 if ($data =~ /<%/) {
134 6 100       30 _prepare_helpers() unless $helpers;
135 6         41 $tmpl = Pinwheel::View::ERB::parse_template($data, $filename);
136 6         287 $data = $tmpl->({}, {}, $helpers);
137             }
138 26         450 return YAML::Tiny->read_string($data)->[0];
139             }
140              
141             sub _prepare_helpers
142             {
143 5     5   11 my ($pkg, $fns);
144              
145 5         10 $fns = {};
146 5         11 $pkg = \%Pinwheel::Helpers::Fixtures::;
147 5         9 foreach (@{$pkg->{'EXPORT_OK'}}) {
  5         26  
148 8 100       33 $fns->{$_} = \&{$pkg->{$_}} if $pkg->{$_};
  6         38  
149             }
150              
151 5         16 $helpers = $fns;
152             }
153              
154              
155             sub identify
156             {
157 27     27 1 79 my ($s) = @_;
158              
159 27 100       126 $ids{$s} = _hash($s) if !exists($ids{$s});
160 27         154 return $ids{$s};
161             }
162              
163              
164             # Implementation of http://burtleburtle.net/bob/hash/evahash.html
165             sub _hash
166             {
167 2     2   3664 use integer;
  2         26  
  2         11  
168 9     9   18 my ($s) = @_;
169 9         14 my ($length, $a, $b, $c, $i, $j, @k);
170              
171 9         16 $length = length($s);
172 9         21 $s .= "\0\0\0\0\0\0\0\0\0\0\0\0";
173 9         306 @k = unpack('V' x (length($s) >> 2), $s);
174              
175 9         18 $i = 0;
176 9         16 $j = ($length >> 2) - 3;
177 9         16 $a = $b = 0x9e3779b9;
178 9         9 $c = 0;
179 9         29 while ($i <= $j) {
180 4         10 $a += $k[$i++];
181 4         8 $b += $k[$i++];
182 4         6 $c += $k[$i++];
183 4         13 ($a, $b, $c) = _mix($a, $b, $c);
184             }
185              
186 9         22 $a += $k[$i++];
187 9         12 $b += $k[$i++];
188 9         16 $c += $length + ($k[$i++] << 8);
189 9         26 ($a, $b, $c) = _mix($a, $b, $c);
190              
191 9 100       27 if ($c & 0x80000000) {
192 4         8 $c = 0x80000000 - ($c & 0x7fffffff);
193             } else {
194 5         13 $c &= 0x7fffffff;
195             }
196              
197 9         37 return $c;
198             }
199              
200             sub _mix
201             {
202 2     2   554 use integer;
  2         5  
  2         10  
203 13     13   18 my ($a, $b, $c) = @_;
204              
205 13         25 $a = ($a - $b - $c) ^ (($c >> 13) & 0x0007ffff);
206 13         23 $b = ($b - $c - $a) ^ ($a << 8);
207 13         22 $c = ($c - $a - $b) ^ (($b >> 13) & 0x0007ffff);
208 13         19 $a = ($a - $b - $c) ^ (($c >> 12) & 0x000fffff);
209 13         18 $b = ($b - $c - $a) ^ ($a << 16);
210 13         21 $c = ($c - $a - $b) ^ (($b >> 5) & 0x07ffffff);
211 13         22 $a = ($a - $b - $c) ^ (($c >> 3) & 0x1fffffff);
212 13         94 $b = ($b - $c - $a) ^ ($a << 10);
213 13         19 $c = ($c - $a - $b) ^ (($b >> 15) & 0x0001ffff);
214              
215 13         36 return ($a, $b, $c);
216             }
217              
218              
219             1;
220              
221             __DATA__