File Coverage

blib/lib/Catmandu/Plugin/SideCar.pm
Criterion Covered Total %
statement 28 28 100.0
branch 2 2 100.0
condition n/a
subroutine 10 10 100.0
pod 0 1 0.0
total 40 41 97.5


line stmt bran cond sub pod time code
1             package Catmandu::Plugin::SideCar;
2              
3 1     1   385853 use Catmandu::Sane;
  1         8  
  1         9  
4              
5             our $VERSION = '1.09';
6              
7 1     1   230 use Catmandu::Util qw(:is);
  1         2  
  1         245  
8 1     1   7 use Hash::Merge::Simple 'merge';
  1         2  
  1         53  
9 1     1   7 use Moo::Role;
  1         1  
  1         10  
10 1     1   530 use Package::Stash;
  1         3  
  1         30  
11 1     1   6 use Carp;
  1         2  
  1         52  
12 1     1   5 use namespace::clean;
  1         2  
  1         5  
13              
14             has sidecar => (
15             is => 'ro',
16             coerce => sub {
17             my $store = $_[0];
18             if (is_string($store)) {
19             Catmandu->store($store);
20             }
21             elsif (is_hash_ref($store)) {
22             my $package = $store->{package};
23             my $options = $store->{options} // +{};
24             Catmandu->store($package, %$options);
25             }
26             else {
27             $store;
28             }
29             }
30             );
31              
32             has sidecar_bag => (is => 'ro', default => sub {'data'});
33              
34             sub BUILD {
35 2     2 0 54 my ($self) = @_;
36              
37 2         29 my $sidecar = $self->sidecar->bag($self->sidecar_bag);
38              
39             # Insert a Catmandu::FileStore 'files' method into Catmandu::Store-s
40 2 100       2457 unless ($self->can('files')) {
41 1         20 my $stash = Package::Stash->new(ref $self);
42             $stash->add_symbol(
43             '&files' => sub {
44 3     3   2084 my ($self, $id) = @_;
        3      
45 3         15 return $sidecar->files($id);
46             }
47 1         57 );
48             }
49             }
50              
51             around get => sub {
52             my ($orig, $self, @args) = @_;
53              
54             my $orig_item = $self->$orig(@args);
55              
56             my $bag_name = $self->sidecar_bag;
57             my $bag = $self->sidecar->bag($bag_name);
58             my $sidecar_item = $bag ? $bag->get(@args) : {};
59              
60             return unless $sidecar_item || $orig_item;
61              
62             merge $sidecar_item , $orig_item // +{};
63             };
64              
65             around add => sub {
66             my ($orig, $self, @args) = @_;
67              
68             my $orig_item = $self->$orig(@args);
69              
70             my $bag_name = $self->sidecar_bag;
71             my $bag = $self->sidecar->bag($bag_name);
72             my $sidecar_item = $bag ? $bag->add(@args) : {};
73              
74             return unless $sidecar_item || $orig_item;
75              
76             merge $sidecar_item , $orig_item // +{};
77             };
78              
79             around delete => sub {
80             my ($orig, $self, @args) = @_;
81              
82             $self->$orig(@args);
83              
84             my $bag_name = $self->sidecar_bag;
85             my $bag = $self->sidecar->bag($bag_name);
86              
87             $bag->delete(@args) if $bag;
88             };
89              
90             around delete_all => sub {
91             my ($orig, $self, @args) = @_;
92              
93             $self->$orig(@args);
94              
95             my $result = {};
96             my $bag_name = $self->sidecar_bag;
97             my $bag = $self->sidecar->bag($bag_name);
98              
99             $bag->delete_all(@args) if $bag;
100             };
101              
102             around drop => sub {
103             my ($orig, $self, @args) = @_;
104              
105             $self->$orig(@args);
106              
107             my $result = {};
108             my $bag_name = $self->sidecar_bag;
109             my $bag = $self->sidecar->bag($bag_name);
110              
111             $bag->drop(@args) if $bag;
112             };
113              
114             around commit => sub {
115             my ($orig, $self, @args) = @_;
116              
117             $self->$orig(@args);
118              
119             my $result = {};
120             my $bag_name = $self->sidecar_bag;
121             my $bag = $self->sidecar->bag($bag_name);
122              
123             $bag->commit(@args) if $bag;
124             };
125              
126             1;
127              
128             __END__
129              
130             =pod
131              
132             =head1 NAME
133              
134             Catmandu::Plugin::SideCar - Automatically update a parallel Catmandu::Store with metadata
135              
136             =head1 SYNOPSIS
137              
138             # Using the command line
139              
140             $ cat catmandu.yml
141             ---
142             store:
143             files:
144             package: File::Simple
145             options:
146             root: /data/test123
147             bags:
148             index:
149             plugins:
150             - SideCar
151             sidecar:
152             package: ElasticSearch
153             options:
154             client: '1_0::Direct'
155             index_name: catmandu
156              
157             ...
158              
159             # Add files to the FileStore in bag 1234
160             $ catmandu stream /tmp/test.txt to files --bag 1234 --id test.txt
161              
162             # Add metadata to the FileStore for bag 1234
163             $ cat metadata.yml
164             ---
165             _id: 1234
166             colors:
167             - red
168             - green
169             - blue
170             name: test
171             ...
172             $ catmandu import YAML to files < metadata.yml
173              
174             # Export the metadata again from the FileStore
175             $ catmandu export files to YAML
176             ---
177             _id: 1234
178             colors:
179             - red
180             - green
181             - blue
182             name: test
183             ...
184              
185             # Or in your Perl program
186             my $store = Catmandu->store('File::Simple',
187             root => 'data/test123'
188             bags => {
189             index => {
190             plugins => [qw(SideCar)],
191             sidecar => {
192             package => "ElasticSearch",
193             options => {
194             client => '1_0::Direct',
195             index_name => 'catmandu',
196             }
197             }
198             }
199             });
200              
201             my $index = $store->index;
202              
203             $index->add({ _id => '1234' , colors => [qw(red green blue)] , name => 'test'});
204              
205             my $files = $index->files('1234');
206             $files->upload(IO::File->new('</tmp/test.txt'), 'test.txt');
207              
208             my $file = $files->get('text.txt');
209              
210             $files->steam(IO::File->new('>/tmp/test.txt'),$file);
211              
212             =head1 DESCRIPTION
213              
214             The Catmandu::Plugin::SideCar can be used to combine L<Catmandu::Store>-s , L<Catmandu::FileStore>-s
215             (and L<Catmandu::Store::Multi> , L<Catmandu::Store::File::Multi>) as one access point.
216             Every get,add,delete,drop and commit action in the store will be first executed in the original
217             store and re-executed in the SideCar store.
218              
219             =head1 COMBINING A FILESTORE WITH A STORE
220              
221             To add metadata to a L<Catmandu::FileStore> a SideCar needs to be added to the C<index>
222             bag of the FileStore:
223              
224             package: File::Simple
225             options:
226             root: /data/test123
227             bags:
228             index:
229             plugins:
230             - SideCar
231             sidecar:
232             package: ElasticSearch
233             options:
234             client: '1_0::Direct'
235             index_name: catmandu
236             sidecar_bag: data
237              
238             =head1 COMBINING A STORE WITH A FILESTORE
239              
240             To add files to a L<Catmandu::Store> a SideCar needs to be added to the bag containing
241             the metadata (by default C<data>):
242              
243             package: ElasticSearch
244             options:
245             client: '1_0::Direct'
246             index_name: catmandu
247             bags:
248             data:
249             plugins:
250             - SideCar
251             sidecar:
252             package: File::Simple
253             options:
254             root: /data/test123
255             uuid: 1
256             sidecar_bag: index
257              
258             Notice that we added for the L<Catmandu::Store::File::Simple> the requires C<uuid> options
259             because the L<Catmandu::Store::ElasticSearch> is using UUIDs as default identifiers.
260              
261             =head1 RESTRICTIONS
262              
263             Some L<Catmandu::FileStore>-s may set restrictions on the C<_id>-s that can be
264             used in records.
265              
266             =head1 CONFIGURATION
267              
268             =over
269              
270             =item sidecar STRING
271              
272             =item sidecar HASH
273              
274             =item sidecar Catmandu::Store | Catmandu::FileStore
275              
276             The pointer to a configured Catmandu::Store or Catmandu::FileStore.
277              
278             =item sidecar_bag
279              
280             The SideCar L<Catmandu::Bag> into which to store the data (default 'bag').
281              
282             =back
283              
284             =head1 SEE ALSO
285              
286             L<Catmandu::Store>, L<Catmandu::Bag>,
287             L<Catmandu::FileStore>
288              
289             =cut