File Coverage

blib/lib/Articulate/Storage/DBIC/Simple.pm
Criterion Covered Total %
statement 18 70 25.7
branch 0 18 0.0
condition n/a
subroutine 6 19 31.5
pod 10 13 76.9
total 34 120 28.3


line stmt bran cond sub pod time code
1             package Articulate::Storage::DBIC::Simple;
2 2     2   3840 use strict;
  2         3  
  2         66  
3 2     2   10 use warnings;
  2         2  
  2         46  
4              
5 2     2   7 use Moo;
  2         2  
  2         10  
6             with 'Articulate::Role::Component';
7             with 'Articulate::Role::Storage';
8 2     2   538 use Articulate::Syntax;
  2         4  
  2         15  
9 2     2   2528 use JSON;
  2         4  
  2         15  
10 2     2   262 use Scalar::Util qw(blessed);
  2         4  
  2         1942  
11              
12             =head1 NAME
13              
14             Articulate::Content::DBIC::Simple - store your content in a simple
15             database
16              
17             =cut
18              
19             =head1 DESCRIPTION
20              
21             This content storage interface works by placing content and metadata in
22             a database table, to which it connects using L.
23              
24             All content items are stored in a single table defined in
25             L,
26             and rows contain meta, content and location. Meta is stored in JSON.
27              
28             It is left up to the application, not the database to maintain
29             referential integrity (although there is a rudimentary cascade deletion
30             for descendant items).
31              
32             On the other hand, you can make changes to your dat structure freely
33             without making schema changes.
34              
35             By default, this will create an SQLite database in memory and deploy
36             the schema (i.e. no persistence), but you can alter this using the
37             C attribute. You can also make your own schema, provided it is
38             a superset of the existing schema.
39              
40             =cut
41              
42             =head1 ATTRIBUTE
43              
44             =head3 schema
45              
46             components:
47             Articulate::Storage::DBIC::Simple:
48             schema:
49             class: Articulate::Storage::DBIC::Simple::Schema
50             constructor: connect
51             args:
52             - dbi:SQLite:somefile.db
53             - user_name
54             - notverysecretpassword
55              
56             Allows you to specify how to connect to your database. By default, it
57             connects to an SQLite :memory: DB and uses the connect_and_deploy
58             constructor from the L
59             schema.
60              
61             =cut
62              
63             has schema => (
64             is => 'rw',
65             default => sub {
66             return {
67             class => 'Articulate::Storage::DBIC::Simple::Schema',
68             constructor => 'connect_and_deploy',
69             args => [ 'dbi:SQLite::memory:', '', '' ],
70             };
71             },
72             coerce => sub {
73             instantiate $_[0],;
74             },
75             );
76              
77             =head1 METHODS
78              
79             =cut
80              
81             sub dbic_find { # internal method
82 0     0 0   my $self = shift;
83 0           my $location = shift;
84 0           my $dbic_item = $self->schema->resultset('Articulate::Item')
85             ->find( { location => "$location" } );
86             }
87              
88             sub dbic_to_real { # internal method
89 0     0 0   my $self = shift;
90 0           my $dbic_item = shift;
91 0           return $self->construction->construct(
92             {
93             location => $dbic_item->location,
94             meta => from_json( $dbic_item->meta ),
95             content => $dbic_item->content,
96             }
97             );
98             }
99              
100             sub real_to_dbic { # internal method
101 0     0 0   my $self = shift;
102 0           my $item = shift;
103 0           my $dbic_item = $self->schema->resultset('Articulate::Item')->new_result(
104             {
105             location => '' . $item->location,
106             content => '' . $item->content,
107             meta => to_json( $item->meta ),
108             }
109             );
110             }
111              
112             =head3 get_item
113              
114             $storage->get_item( 'zone/public/article/hello-world' )
115              
116             Retrieves the metadata for the content at that location.
117              
118             =cut
119              
120             sub get_item {
121 0     0 1   my $self = shift;
122 0           my $location = shift->location;
123 0 0         throw_error Internal => "Bad location $location"
124             unless $self->navigation->valid_location($location);
125 0 0         my $dbic_item = $self->dbic_find($location)
126             or throw_error NotFound => "No item at $location";
127 0           return $self->dbic_to_real($dbic_item);
128             }
129              
130             =head3 get_meta
131              
132             $storage->get_meta( 'zone/public/article/hello-world' )
133              
134             Retrieves the metadata for the content at that location.
135              
136             =cut
137              
138             sub get_meta {
139 0     0 1   my $self = shift;
140 0           my $item = shift;
141 0           my $location = $item->location;
142 0 0         throw_error Internal => "Bad location $location"
143             unless $self->navigation->valid_location($location);
144 0 0         my $dbic_item = $self->dbic_find($location)
145             or throw_error NotFound => "No item at $location";
146 0           return $self->dbic_to_real($dbic_item)->meta;
147             }
148              
149             =head3 set_meta
150              
151             $storage->set_meta( 'zone/public/article/hello-world', {...} )
152              
153             Sets the metadata for the content at that location.
154              
155             =cut
156              
157             sub set_meta {
158             my $self = shift;
159             my $item = shift;
160             my $location = $item->location;
161             throw_error Internal => "Bad location $location"
162             unless $self->navigation->valid_location($location);
163             my $dbic_item = $self->dbic_find($location)
164             or throw_error NotFound => "No item at $location";
165             $dbic_item->meta( to_json( $item->meta ) );
166             $dbic_item->update;
167             return $item;
168             }
169              
170             =head3 patch_meta
171              
172             $storage->patch_meta( 'zone/public/article/hello-world', {...} )
173              
174             Alters the metadata for the content at that location. Existing keys are
175             retained.
176              
177             CURRENTLY this affects top-level keys only, but a descent algorigthm is
178             planned.
179              
180             =cut
181              
182             sub patch_meta {
183 0     0 1   die 'not implemented';
184             }
185              
186             =head3 get_settings
187              
188             $storage->get_settings('zone/public/article/hello-world')
189              
190             Retrieves the settings for the content at that location.
191              
192             =cut
193              
194             sub get_settings {
195 0     0 1   die 'not implemented';
196             }
197              
198             =head3 set_settings
199              
200             $storage->set_settings('zone/public/article/hello-world', $amended_settings)
201              
202             Retrieves the settings for the content at that location.
203              
204             =cut
205              
206             sub set_settings {
207 0     0 1   die 'not implemented';
208             }
209              
210             =head3 get_settings_complete
211              
212             $storage->get_settings_complete('zone/public/article/hello-world')
213              
214             Retrieves the settings for the content at that location.
215              
216             =cut
217              
218             sub get_settings_complete {
219 0     0 1   die 'not implemented';
220             }
221              
222             =head3 get_content
223              
224             $storage->get_content('zone/public/article/hello-world')
225              
226             Retrieves the content at that location.
227              
228             =cut
229              
230             sub get_content {
231 0     0 1   my $self = shift;
232 0           my $item = shift;
233 0           my $location = $item->location;
234 0 0         throw_error Internal => "Bad location $location"
235             unless $self->navigation->valid_location($location);
236 0 0         my $dbic_item = $self->dbic_find($location)
237             or throw_error NotFound => "No item at $location";
238 0           return $self->dbic_to_real($dbic_item)->content;
239             }
240              
241             =head3 set_content
242              
243             $storage->set_content('zone/public/article/hello-world', $blob);
244              
245             Places content at that location.
246              
247             =cut
248              
249             sub set_content {
250             my $self = shift;
251             my $item = shift;
252             my $location = $item->location;
253             throw_error Internal => "Bad location $location"
254             unless $self->navigation->valid_location($location);
255             my $dbic_item = $self->dbic_find($location)
256             or throw_error NotFound => "No item at $location";
257             $dbic_item->content( $item->content );
258             $dbic_item->update;
259             return $item;
260             }
261              
262             =head3 create_item
263              
264             $storage->create_item('zone/public/article/hello-world', $meta, $blob);
265              
266             Places meta and content at that location.
267              
268             =cut
269              
270             sub create_item {
271 0     0 1   my $self = shift;
272 0           my $item = shift;
273 0           my $location = $item->location;
274 0 0         throw_error Internal => "Bad location " . $location
275             unless $self->navigation->valid_location($location);
276 0 0         throw_error AlreadyExists => "Cannot create: item already exists at "
277             . $location
278             if $self->item_exists($location);
279 0           my $dbic_item = $self->real_to_dbic($item);
280 0           $dbic_item->insert();
281 0           return $item;
282             }
283              
284             =head3 item_exists
285              
286             if ($storage->item_exists( 'zone/public/article/hello-world')) {
287             ...
288             }
289              
290             Determines if the item has been created (only the C is
291             tested).
292              
293             =cut
294              
295             sub item_exists {
296 0     0 1   my $self = shift;
297 0           my $location = shift->location;
298 0 0         throw_error Internal => "Bad location $location"
299             unless $self->navigation->valid_location($location);
300 0           !!$self->dbic_find($location);
301             }
302              
303             =head3 list_items
304              
305             $storage->list_items ('/zone/public'); # 'hello-world', 'second-item' )
306              
307             Returns a list of items in the.
308              
309             =cut
310              
311             sub list_items {
312 0     0 1   my $self = shift;
313 0           my $item = shift;
314 0           my $location = $item->location;
315 0           my $qm_location = $item->location;
316 0           my $dbic_items = $self->schema->resultset('Articulate::Item')
317             ->search( { location => { like => $qm_location . '%' } } );
318 0           my $location_specification = new_location_specification( $location . '/*' );
319 0           return map { $_->[-1] }
  0            
320 0           grep { $location_specification->matches($_); }
321 0           map { new_location( $_->location ) } $dbic_items->all;
322             }
323              
324             =head3 empty_all_content
325              
326             $storage->empty_all_content;
327              
328             Removes all content. This is totally irreversible, unless you took a
329             backup!
330              
331             =cut
332              
333             sub empty_all_content {
334             my $self = shift;
335             $self->schema->resultset('Articulate::Item')->delete();
336             }
337              
338             =head3 delete_item
339              
340             $storage->delete_item ('/zone/public');
341              
342             Deletes the item and all its descendants.
343              
344             =cut
345              
346             sub delete_item {
347             my $self = shift;
348             my $item = shift;
349             my $qm_location = $item->location;
350             my $dbic_items = $self->schema->resultset('Articulate::Item')
351             ->search( { location => { like => "$qm_location\%" } } );
352              
353             # todo: fix case of "foo" matches "foobar"
354             $dbic_items->count
355             or throw_error NotFound => 'Item does not exist at ' . $item->location;
356             $dbic_items->delete();
357             }
358              
359             =head1 SEE ALSO
360              
361             =over
362              
363             =item * L
364              
365             =item * L
366              
367             =item * L
368              
369             =item * L
370              
371             =item * L
372              
373             =back
374              
375             =cut
376              
377             1;