File Coverage

lib/DBIx/EAV.pm
Criterion Covered Total %
statement 144 145 99.3
branch 42 56 75.0
condition 11 15 73.3
subroutine 23 23 100.0
pod 4 5 80.0
total 224 244 91.8


line stmt bran cond sub pod time code
1             package DBIx::EAV;
2              
3 10     10   1172625 use Moo;
  10         81749  
  10         38  
4 10     10   14005 use strictures 2;
  10         11262  
  10         335  
5 10     10   1590 use DBI;
  10         16  
  10         280  
6 10     10   6799 use Lingua::EN::Inflect ();
  10         176259  
  10         446  
7 10     10   73 use Data::Dumper;
  10         12  
  10         514  
8 10     10   3344 use DBIx::EAV::EntityType;
  10         22  
  10         285  
9 10     10   3559 use DBIx::EAV::Entity;
  10         19  
  10         240  
10 10     10   3129 use DBIx::EAV::ResultSet;
  10         167  
  10         320  
11 10     10   3452 use DBIx::EAV::Schema;
  10         22  
  10         326  
12 10     10   49 use Carp qw' croak confess ';
  10         10  
  10         517  
13 10     10   39 use Scalar::Util 'blessed';
  10         13  
  10         394  
14 10     10   4309 use Class::Load qw' try_load_class ';
  10         95829  
  10         507  
15 10     10   3887 use namespace::clean;
  10         38319  
  10         34  
16              
17             our $VERSION = "0.09";
18              
19             # required
20             has 'dbh', is => 'ro', required => 1;
21              
22             # options
23             has 'default_attribute_type', is => 'ro', default => 'varchar';
24             has 'schema_config', is => 'ro', default => sub { {} };
25             has 'entity_namespaces', is => 'ro', default => sub { [] };
26             has 'resultset_namespaces', is => 'ro', default => sub { [] };
27              
28             # internal
29             has 'schema', is => 'ro', lazy => 1, builder => 1, init_arg => undef, handles => [qw/ table dbh_do /];
30             has '_types', is => 'ro', default => sub { {} };
31             has '_types_by_id', is => 'ro', default => sub { {} };
32              
33             # group schema_config params
34             around BUILDARGS => sub {
35             my ( $orig, $class, @args ) = @_;
36             my $params = @args == 1 && ref $args[0] ? $args[0] : { @args };
37             my $schema_config = delete $params->{schema_config} || {};
38              
39             my @schema_params = grep { exists $params->{$_} } qw/
40             tenant_id data_types database_cascade_delete static_attributes
41             table_prefix id_type default_attribute_type
42             /;
43              
44             @{$schema_config}{@schema_params} = delete @{$params}{@schema_params};
45              
46             $class->$orig(%$params, schema_config => $schema_config);
47             };
48              
49              
50             sub _build_schema {
51 12     12   2959 my $self = shift;
52 12         19 DBIx::EAV::Schema->new(%{$self->schema_config}, dbh => $self->dbh);
  12         189  
53             }
54              
55             sub connect {
56 1     1 1 72 my ($class, $dsn, $user, $pass, $attrs, $constructor_params) = @_;
57              
58 1 50       3 croak 'Missing $dsn argument for connect()' unless $dsn;
59              
60 1 50       2 croak "connect() must be called as a class method."
61             if ref $class;
62              
63 1   50     5 $constructor_params //= {};
64              
65 1 50       6 $constructor_params->{dbh} = DBI->connect($dsn, $user, $pass, $attrs)
66             or die $DBI::errstr;
67              
68 1         6966 $class->new($constructor_params);
69             }
70              
71             sub type {
72 238     238 1 9209 my ($self, $value) = @_;
73              
74             return $self->_types->{$value}
75 238 100       3097 if exists $self->_types->{$value};
76              
77 7         21 my $type = $self->_load_type('name', $value);
78              
79             # not registered, try to find a custom entity class and register it
80 7 100 100     37 if (!$type && (my $entity_class = $self->_resolve_entity_class($value))) {
81              
82 3         9 ($type) = $self->register_types({$value => $entity_class->type_definition});
83             }
84              
85 6 100       511 confess "EntityType '$value' does not exist."
86             unless $type;
87              
88 4         8 $type;
89             }
90              
91             sub type_by_id {
92 47     47 0 57 my ($self, $value) = @_;
93              
94             return $self->_types_by_id->{$value}
95 47 100       225 if exists $self->_types_by_id->{$value};
96              
97 3 50       14 $self->_load_type('id', $value)
98             or confess "EntityType 'id=$value' does not exist.";
99             }
100              
101             sub _load_type {
102 10     10   14 my ($self, $field, $value) = @_;
103              
104 10         197 my $type_row = $self->table('entity_types')->select_one({ $field => $value });
105 10 100       220 return unless $type_row;
106              
107 4         45 my $type = DBIx::EAV::EntityType->load({ %$type_row, core => $self});
108 4         17 $self->_types->{$type->name} = $type;
109 4         10 $self->_types_by_id->{$type->id} = $type;
110 4         14 $type;
111             }
112              
113             sub _resolve_entity_class {
114 109     109   130 my ($self, $name) = @_;
115              
116 109         109 foreach my $ns (@{ $self->entity_namespaces }) {
  109         277  
117              
118 5         12 my $entity_class = join '::', $ns, $name;
119 5         17 my ($is_loaded, $error) = try_load_class $entity_class;
120              
121 5 100       567 return $entity_class if $is_loaded;
122              
123             # rethrow compilation errors
124 1 50       14 die $error if $error =~ /^Can't locate .* in \@INC/;
125             }
126              
127 104         1997 return;
128             }
129              
130             sub _resolve_resultset_class {
131 118     118   130 my ($self, $name) = @_;
132              
133 118         111 foreach my $ns (@{ $self->resultset_namespaces }) {
  118         278  
134              
135 2         4 my $class = join '::', $ns, $name;
136 2         7 my ($is_loaded, $error) = try_load_class $class;
137              
138 2 50       723 return $class if $is_loaded;
139              
140             # rethrow compilation errors
141 0         0 die $class;
142             }
143              
144 116         405 return;
145             }
146              
147             sub resultset {
148 118     118 1 7486 my ($self, $name) = @_;
149              
150 118   100     215 my $rs_class = $self->_resolve_resultset_class($name)
151             || 'DBIx::EAV::ResultSet';
152              
153 118         215 $rs_class->new({
154             eav => $self,
155             type => $self->type($name),
156             });
157             }
158              
159              
160             sub register_types {
161 12     12 1 84185 my ($self, $schema) = @_;
162 12         22 my %skip;
163              
164             # register only not-installed entities to
165             # allow multiple calls to this method
166 12         43 my @new_types = grep { not exists $self->_types->{$_} } keys %$schema;
  41         127  
167              
168             # create or update each entity type on database
169 12         21 my @registered_types;
170 12         24 foreach my $name (@new_types) {
171 41 100       622 next if exists $self->_types->{$name};
172             push @registered_types,
173 39         128 $self->_register_entity_type($name, $schema->{$name}, $schema);
174             }
175              
176             # register relationships
177 12         192 foreach my $name (@new_types) {
178              
179 41         52 my $spec = $schema->{$name};
180 41         83 my $entity_type = $self->type($name);
181              
182 41         53 foreach my $reltype (qw/ has_one has_many many_to_many /) {
183              
184 123 100       243 next unless defined $spec->{$reltype};
185              
186 34   50     81 $spec->{$reltype} ||= [];
187             $spec->{$reltype} = [$spec->{$reltype}]
188 34 100       90 unless ref $spec->{$reltype} eq 'ARRAY';
189              
190 34         46 foreach my $rel (@{$spec->{$reltype}}) {
  34         63  
191 34         86 $entity_type->register_relationship($reltype, $rel);
192             }
193             }
194             }
195              
196 12         45 @registered_types;
197             }
198              
199              
200             sub _register_entity_type {
201 41     41   66 my ($self, $name, $spec, $schema) = @_;
202              
203             # parent type first
204 41         39 my $parent_type;
205 41 100       100 if ($spec->{extends}) {
206              
207 5 100       19 unless ($parent_type = $self->_types->{$spec->{extends}}) {
208              
209             die "Unknown type '$spec->{extends}' specified in 'extents' option for type '$name'."
210 2 50       5 unless exists $schema->{$spec->{extends}};
211              
212 2         9 $parent_type = $self->_register_entity_type($spec->{extends}, $schema->{$spec->{extends}}, $schema);
213             }
214             }
215              
216             # find or create entity type
217 41         641 my $types_table = $self->table('entity_types');
218 41         699 my $hierarchy_table = $self->table('type_hierarchy');
219 41         237 my $type = $types_table->select_one({ name => $name });
220              
221 41 50       479 if (defined $type) {
222              
223             # change parent
224             }
225             else {
226              
227             # TODO implement rename
228             # if ($spec->{rename_from}) { ... }
229              
230 41         163 my $id = $types_table->insert({ name => $name });
231 41         189 $type = $types_table->select_one({ id => $id });
232 41 50       468 die "Error inserting entity type '$name'!" unless $type;
233              
234 41 100       96 if ($parent_type) {
235             $hierarchy_table->insert({
236             parent_type_id => $parent_type->{id},
237             child_type_id => $type->{id}
238 5         26 });
239              
240 5         14 $type->{parent} = $parent_type;
241             }
242             }
243              
244             # update or create attributes
245 41         931 my $attributes = $self->table('attributes');
246 41         124 my %static_attributes = map { $_ => {name => $_, is_static => 1} } @{$self->table('entities')->columns};
  124         379  
  41         603  
247 41         115 $type->{attributes} = {};
248              
249 41 100       106 my %inherited_attributes = $parent_type ? map { $_->{name} => $_ } $parent_type->attributes( no_static => 1 )
  21         32  
250             : ();
251              
252 41         46 foreach my $attr_spec (@{$spec->{attributes}}) {
  41         108  
253              
254             # expand string to name/type
255 112 100       235 unless (ref $attr_spec) {
256 45         153 my ($name, $type) = split ':', $attr_spec;
257 45   66     235 $attr_spec = {
258             name => $name,
259             type => $type || $self->default_attribute_type
260             };
261             }
262              
263             die sprintf("Error registering attribute '%s' for entity '%s'. Can't use names of static attributes (real table columns).", $attr_spec->{name}, $type->{name})
264 112 50       257 if exists $static_attributes{$attr_spec->{name}};
265              
266             printf STDERR "[warn] entity '%s' is overriding inherited attribute '%s'", $name, $attr_spec->{name}
267 112 50       204 if $inherited_attributes{$attr_spec->{name}};
268              
269             my $attr = $attributes->select_one({
270             entity_type_id => $type->{id},
271             name => $attr_spec->{name}
272 112         373 });
273              
274 112 50       1166 if (defined $attr) {
275             # update
276             }
277             else {
278 112         153 delete $attr_spec->{id}; # safety
279              
280 112         411 my %data = %$attr_spec;
281              
282 112         170 $data{entity_type_id} = $type->{id};
283 112   66     356 $data{data_type} = delete($data{type}) || $self->default_attribute_type;
284              
285             die sprintf("Attribute '%s' has unknown data type '%s'.", $data{name}, $data{data_type})
286 112 50       2508 unless $self->schema->has_data_type($data{data_type});
287              
288 112         273 $attributes->insert(\%data);
289 112         285 $attr = $attributes->select_one(\%data);
290 112 50       1388 die "Error inserting attribute '$attr_spec->{name}'!" unless $attr;
291             }
292              
293 112         377 $type->{attributes}{$attr->{name}} = $attr;
294             }
295              
296             $self->_types->{$name} =
297 41         921 $self->_types_by_id->{$type->{id}} = DBIx::EAV::EntityType->new(%$type, core => $self);
298             }
299              
300              
301              
302             1;
303              
304             __END__