File Coverage

blib/lib/EntityModel.pm
Criterion Covered Total %
statement 62 154 40.2
branch 5 24 20.8
condition 2 3 66.6
subroutine 15 28 53.5
pod 13 15 86.6
total 97 224 43.3


line stmt bran cond sub pod time code
1             package EntityModel;
2             # ABSTRACT: Cross-language event-driven ORM
3              
4             use EntityModel::Class {
5 14         352 _isa => [qw(EntityModel::Model)],
6             name => { type => 'string' },
7             plugin => { type => 'array', subclass => 'EntityModel::Plugin' },
8             support => { type => 'array', subclass => 'EntityModel::Support' },
9             storage => { type => 'array', subclass => 'EntityModel::Storage' },
10             storage_queued => { type => 'array', subclass => 'EntityModel::Storage' },
11             cache => { type => 'array', subclass => 'EntityModel::Cache' },
12             cache_queued => { type => 'array', subclass => 'EntityModel::Cache' },
13             db => { type => 'EntityModel::DB' },
14 14     14   417590 };
  14         503950  
15 14     14   7391 no if $] >= 5.017011, warnings => "experimental::smartmatch";
  14         34  
  14         108  
16              
17             our $VERSION = '0.102';
18              
19             =head1 NAME
20              
21             EntityModel - manage entity model definitions
22              
23             =head1 VERSION
24              
25             version 0.102
26              
27             =head1 SYNOPSIS
28              
29             use EntityModel;
30             # Define model
31             my $model = EntityModel->new->load_from(
32             JSON => { entity : [
33             { name : 'article', field : [
34             { name : 'idarticle', type : 'bigserial' },
35             { name : 'title', type : 'varchar' },
36             { name : 'content', type : 'text' }
37             ], primary => { field : [ 'idarticle' ], separator : ':' }
38             ] }
39             );
40             # Apply PostgreSQL schema (optional, only needed if the model changes)
41             $model->apply('PostgreSQL' => { schema => 'datamodel', host => 'localhost', user => 'testuser' });
42             # Create Perl classes
43             $model->apply('Perl' => { namespace => 'Entity', baseclass => 'EntityModel::EntityBase' });
44              
45             my $article = Entity::Article->create(
46             title => 'Test article',
47             content => 'Article content'
48             )->done(sub {
49             my $article = shift;
50             say "ID was " . $article->id;
51             })->fail(sub {
52             die 'Failed to create new article';
53             });
54             Entity::Article->find(
55             title => 'Test article'
56             )->first(sub {
57             my $match = shift;
58             $match->title('Revised title');
59             die "Instances of the same object should always be linked, consistent and up-to-date"
60             unless $article->title eq $match->title;
61             });
62              
63             =head1 DESCRIPTION
64              
65             This module provides a data storage abstraction system (in the form of an Object Relational Model) for accessing
66             backend storage from Perl and other languages. The intent is to take a model definition and generate or update
67             database tables, caching layer and the corresponding code (Perl/C++/JS) for accessing data.
68              
69             A brief comparison and list of alternatives is in the L and L sections, please check there
70             before investing any time into using this module.
71              
72             Eventually a full set of documentation will be added to L
73             but for now see the examples further down in this document.
74              
75             =head1 METHODS
76              
77             =cut
78              
79 14     14   1231 use Module::Load ();
  14         31  
  14         283  
80              
81 14     14   82 use EntityModel::Transaction;
  14         23  
  14         388  
82 14     14   108 use EntityModel::Entity;
  14         95  
  14         111  
83 14     14   347 use EntityModel::Field;
  14         30  
  14         107  
84 14     14   344 use EntityModel::Query;
  14         38  
  14         78  
85              
86             =head2 new
87              
88             Constructor. Given a set of options, will load any plugins specified (and/or the defaults), applying
89             other config options via the appropriate plugins.
90              
91             Typically run without options:
92              
93             my $model = EntityModel->new;
94              
95             The exciting things happen elsewhere. See:
96              
97             =over 4
98              
99             =item * L
100              
101             =item * L
102              
103             =item * L
104              
105             =back
106              
107             =cut
108              
109             sub new {
110 13     13 1 15886 my $class = shift;
111              
112 13         32 my @def;
113 13 50       205 if(ref $_[0] ~~ 'HASH') {
    50          
114 0         0 @def = %{$_[0]};
  0         0  
115             } elsif(ref $_[0] ~~ 'ARRAY') {
116 0         0 @def = @{$_[0]};
  0         0  
117             } else {
118 13         44 @def = @_;
119             }
120              
121 13         42 my $self = bless { }, $class;
122              
123             # Apply plugins and options
124 13         71 while(@def) {
125 0         0 my ($k, $v) = splice @def, 0, 2;
126 0         0 $self->load_plugin($k => $v);
127             }
128              
129 13         116 return $self;
130             }
131              
132             =head2 load_from
133              
134             Read in a model definition from the given L-based source.
135              
136             Parameters:
137              
138             =over 4
139              
140             =item * Type - must be a valid L subclass, such as 'Perl', 'JSON' or 'XML'.
141              
142             =item * Definition - dependent on the subclass, typically the filename or raw string data.
143              
144             =back
145              
146             Common usage includes reading from inline Perl:
147              
148             $model->load_from(
149             Perl => {
150             name => 'kvstore',
151             entity => [
152             name => 'object',
153             primary => 'iditem',
154             field => [
155             { name => 'iditem', type => 'bigserial' },
156             { name => 'key', type => 'varchar' },
157             { name => 'value', type => 'varchar' },
158             ],
159             ],
160             }
161             );
162              
163             or the equivalent from JSON:
164              
165             $model->load_from(
166             JSON => \q{
167             "name" : "kvstore",
168             "entity" : [
169             "name" : "object",
170             "primary" : "iditem",
171             "field" : [
172             { "name" : "iditem", "type" : "bigserial" },
173             { "name" : "key", "type" : "varchar" },
174             { "name" : "value", "type" : "varchar" }
175             ]
176             ]
177             }
178             );
179              
180             =cut
181              
182             sub load_from {
183 9     9 1 3495 my $self = shift;
184 9         24 my ($type, $value) = @_;
185              
186 9         30 my $class = "EntityModel::Definition::$type";
187 9         43 $self->load_component($class);
188              
189 6         76 $class->new->load(
190             model => $self,
191             source => $value
192             );
193 6         116 return $self;
194             }
195              
196             =head2 save_to
197              
198             Saves the current model definition to a definition.
199              
200             Parameters:
201              
202             =over 4
203              
204             =item * Type - must be a valid L subclass, such as 'Perl', 'JSON' or 'XML'.
205              
206             =item * Definition - dependent on the subclass, typically the filename or scalarref to hold raw string data.
207              
208             =back
209              
210             You might use something like this to store the current model to a file in JSON format:
211              
212             $model->save_to(
213             JSON => 'model.json'
214             );
215              
216             or this to copy everything from a source model to a target model (wiping everything
217             in the target in the process):
218              
219             my $target = EntityModel->new;
220             $source->save_to(
221             model => $target
222             );
223              
224             =cut
225              
226             sub save_to {
227 0     0 1 0 my $self = shift;
228 0         0 my ($type, $value) = @_;
229              
230 0         0 my $class = "EntityModel::Definition::$type";
231 0         0 $self->load_component($class);
232              
233 0         0 $class->new->save(
234             model => $self,
235             target => $value
236             );
237 0         0 return $self;
238             }
239              
240             =head2 load_component
241              
242             Brings in the given component if it hasn't already been loaded.
243              
244             Typically used by internal methods only.
245              
246             =cut
247              
248             sub load_component {
249 9     9 1 18 my $self = shift;
250 9         18 my $class = shift;
251 9 50       132 unless($class->can('new')) {
252 9         61 Module::Load::load($class);
253 6         173 $class->register;
254             }
255 6         16 return $self;
256             }
257              
258             =head2 add_support
259              
260             Bring in a new L class for this L.
261              
262             Example:
263              
264             $model->add_support(Perl => { namespace => 'Entity' });
265              
266             =cut
267              
268             sub add_support {
269 0     0 1 0 my ($self, $name, $v) = @_;
270 0         0 logDebug("Load support for [%s]", $name);
271 0         0 my $class = 'EntityModel::Support::' . $name;
272 0         0 $self->load_component($class);
273              
274 0         0 my $obj = $class->new($self, $v);
275 0         0 $obj->setup($v);
276 0         0 $obj->apply_model($self);
277 0         0 $self->support->push($obj);
278 0         0 return $self;
279             }
280              
281             =head2 add_storage
282              
283             Add backend storage provided by an L subclass.
284              
285             Example:
286              
287             $model->add_storage(PostgreSQL => { service => ... });
288              
289             =cut
290              
291             sub add_storage {
292 6     6 1 14996 my ($self, $name, $v) = @_;
293 6         29 delete $self->{backend_ready};
294 6         40 logDebug("Load storage for [%s]", $name);
295 6         306 my $class = $name;
296 6 0       13 try { Module::Load::load($class) } unless try { $class->can('new') };
  0         0  
  6         970  
297 0 0       0 unless(try { $class->can('new') }) {
  0         0  
298 0         0 $class = 'EntityModel::Storage::' . $name;
299 0         0 Module::Load::load($class);
300             }
301              
302 0         0 my $obj = $class->new($self, $v);
303 0         0 $self->storage_queued->push($obj);
304             $self->add_handler_for_event(
305             backend_ready => sub {
306 0     0   0 my $self = shift;
307 0         0 $self->{backend_ready} = 1;
308 0         0 0 # one-shot
309             }
310 0         0 );
311 0         0 $obj->setup($v);
312             $obj->wait_for_backend($self->sap( sub {
313 0     0   0 my ($model, $obj) = @_;
314 0         0 $obj->apply_model($model);
315 0         0 $model->storage->push($model->storage_queued->shift);
316 0 0       0 $model->invoke_event(backend_ready =>) unless $model->storage_queued->count;
317 0         0 0; # one-shot event
318 0         0 }));
319 0         0 return $self;
320             }
321              
322             =head2 backend_ready
323              
324             Returns true if all storage and cache backends are ready, false otherwise.
325              
326             =cut
327              
328 0     0 1 0 sub backend_ready { shift->{backend_ready} }
329              
330             =head2 wait_for_backend
331              
332             Requests an event to run after all backends signal readiness.
333              
334             =cut
335              
336             sub wait_for_backend {
337 0     0 1 0 my $self = shift;
338 0         0 my $code = shift;
339 0 0       0 return $code->($self) if $self->backend_ready;
340 0     0   0 $self->add_handler_for_event( backend_ready => sub { $code->(@_); 0 });
  0         0  
  0         0  
341 0         0 return $self;
342             }
343              
344             =head2 add_cache
345              
346             Add backend cache provided by an L subclass.
347              
348             Example:
349              
350             $model->add_cache(PostgreSQL => { service => ... });
351              
352             =cut
353              
354             sub add_cache {
355 0     0 1 0 my ($self, $name, $v) = @_;
356 0         0 logDebug("Load cache for [%s]", $name);
357 0         0 my $class = $name;
358 0 0       0 try { Module::Load::load($class) } unless try { $class->can('new') };
  0         0  
  0         0  
359 0 0       0 unless(try { $class->can('new') }) {
  0         0  
360 0         0 $class = 'EntityModel::Cache::' . $name;
361 0         0 Module::Load::load($class);
362             }
363              
364 0         0 my $obj = $class->new($self, $v);
365 0         0 $obj->setup($v);
366 0         0 $obj->apply_model($self);
367 0         0 $self->cache->push($obj);
368 0         0 return $self;
369             }
370              
371             =head2 add_plugin
372              
373             Adds a plugin. Currently the definition of a 'plugin' is somewhat nebulous,
374             but L is one example.
375              
376             =cut
377              
378             sub add_plugin {
379 1     1 1 548 my $self = shift;
380 1         4 my ($name, $v) = @_;
381 1         2 my $plugin;
382 1 50       4 if(eval { $name->isa('EntityModel::Plugin') }) {
  1         6  
383 1         19 $plugin = $name;
384             } else {
385 0         0 my $mod = 'EntityModel::' . $name;
386 0         0 Module::Load::load($mod);
387 0         0 $plugin = $mod->new;
388             }
389 1         7 $plugin->register($self);
390 1         8 $self->plugin->push($plugin);
391 1         341 return $self;
392             }
393              
394             =head2 transaction
395              
396             Run the coderef in a transaction.
397              
398             Notifies all the attached L instances that we want a transaction, runs the
399             code, then signals end-of-transaction.
400              
401             =cut
402              
403             sub transaction {
404 0     0 1 0 my $self = shift;
405 0         0 my $code = shift;
406 0         0 return EntityModel::Transaction->new(
407             code => $code,
408             model => $self,
409             param => [ @_ ]
410             );
411             }
412              
413             =head2 load_plugin
414              
415             Used internally, see L. Will disappear in the future.
416              
417             =cut
418              
419             sub load_plugin {
420 0     0 1 0 my ($self, $name, $v) = @_;
421 0         0 logDebug("Load plugin [%s]", $name);
422 0         0 my $class = 'EntityModel::Plugin::' . $name;
423 0 0       0 unless(eval { $class->can('new') }) {
  0         0  
424 0         0 Module::Load::load($class);
425             }
426 0         0 logDebug("Activating plugin [%s]", $name);
427 0         0 my $obj = $class->new($self, $v);
428 0         0 $obj->setup($v);
429 0         0 $self->plugin->push($obj);
430 0         0 return $self;
431             }
432              
433             =head2 handler_for
434              
435             Returns the handler for a given entry in the L.
436              
437             =cut
438              
439             sub handler_for {
440 0     0 1 0 my $self = shift;
441 0         0 my $name = shift;
442 0         0 logDebug("Check for handlers for [%s] node", $name);
443 0         0 my @handler;
444             $self->plugin->each(sub {
445 0     0   0 push @handler, $_[0]->handler_for($name);
446 0         0 });
447 0         0 return @handler;
448             }
449              
450             sub defer {
451 0     0 0 0 my $self = shift;
452 0         0 my $code = shift;
453              
454             }
455              
456             {
457             my $model;
458             sub default_model {
459 3     3 0 17 my $class = shift;
460 3 50       15 if(@_) {
461 0         0 my $old_model = $model;
462 0         0 $model = shift;
463 0         0 return $old_model;
464             }
465 3   66     35 $model ||= EntityModel->new;
466 3         17 return $model
467             }
468             }
469              
470             =head2 DESTROY
471              
472             Unload all plugins on exit.
473              
474             =cut
475              
476             sub DESTROY {
477 11     11   9439 my $self = shift;
478             $self->plugin->each(sub {
479 1     1   28 $_[0]->unload;
480 11         75 });
481             }
482              
483             1;
484              
485             __END__