File Coverage

blib/lib/Elastic/Model/Scope.pm
Criterion Covered Total %
statement 15 45 33.3
branch 0 22 0.0
condition 0 2 0.0
subroutine 5 9 55.5
pod 3 4 75.0
total 23 82 28.0


line stmt bran cond sub pod time code
1             package Elastic::Model::Scope;
2             $Elastic::Model::Scope::VERSION = '0.52';
3 1     1   988 use Moose;
  1         3  
  1         8  
4 1     1   4584 use namespace::autoclean;
  1         3  
  1         12  
5 1     1   77 use MooseX::Types::Moose qw(HashRef);
  1         2  
  1         14  
6 1     1   5186 use Scalar::Util qw(refaddr);
  1         2  
  1         60  
7 1     1   7 use Devel::GlobalDestruction;
  1         3  
  1         11  
8              
9             #===================================
10             has '_objects' => (
11             #===================================
12                 is => 'ro',
13                 isa => HashRef,
14                 default => sub { {} },
15             );
16              
17             #===================================
18             has 'parent' => (
19             #===================================
20                 is => 'ro',
21                 isa => 'Elastic::Model::Scope',
22             );
23              
24             # if the object exists in the current scope
25             # return undef if the object is Deleted
26             # return the object if its version is the same or higher
27             # otherwise return undef
28             # otherwise, look for the same object in a parent scope
29             # and, if found, create a clone in the current scope
30              
31             #===================================
32             sub get_object {
33             #===================================
34 0     0 1       my ( $self, $ns, $uid ) = @_;
35 0               my $existing = $self->_objects->{$ns}{ $uid->cache_key };
36              
37 0 0             if ($existing) {
38 0 0                 return if $existing->isa('Elastic::Model::Deleted');
39 0 0 0               return $existing if $existing->uid->version >= ( $uid->version || 0 );
40                 }
41              
42 0 0             my $parent = $self->parent or return undef;
43 0 0             $existing = $parent->get_object( $ns, $uid ) or return undef;
44              
45 0               my $new = Class::MOP::class_of($existing)
46                     ->new_stub( $existing->uid->clone, $existing->_source );
47              
48 0               return $self->store_object( $ns, $new );
49             }
50              
51             # if the object exists in the current scope
52             # return the same object if the version is the same or higher
53             # if the existing object is not Deleted and has not already been looked at
54             # then update it with current details, and return it
55             # else move the old version to 'old'
56             # store the new version in current scope
57              
58             #===================================
59             sub store_object {
60             #===================================
61 0     0 1       my ( $self, $ns, $object ) = @_;
62 0               my $uid = $object->uid;
63 0               my $objects = $self->_objects;
64              
65 0 0             if ( my $existing = $objects->{$ns}{ $uid->cache_key } ) {
66 0 0                 return $existing if $existing->uid->version >= $uid->version;
67 0 0                 unless ( $existing->isa('Elastic::Model::Deleted') ) {
68              
69 0 0                     if ( $existing->_can_inflate ) {
70 0                           $existing->_set_source( $object->_source );
71 0                           $existing->uid->update_from_uid($uid);
72 0                           return $existing;
73                         }
74                     }
75 0                   $objects->{old}{ $uid->cache_key . refaddr $existing} = $existing;
76              
77                 }
78              
79 0               $self->_objects->{$ns}{ $uid->cache_key } = $object;
80             }
81              
82             # If the object exists in the current scope
83             # then rebless it into Elastic::Model::Deleted
84             # Otherwise create a new Elastic::Model::Deleted object
85             # and store it in the current scope
86              
87             #===================================
88             sub delete_object {
89             #===================================
90 0     0 1       my ( $self, $ns, $uid ) = @_;
91              
92 0               my $objects = $self->_objects;
93 0 0             if ( my $existing = $objects->{$ns}{ $uid->cache_key } ) {
94 0                   bless $existing, 'Elastic::Model::Deleted';
95                 }
96                 else {
97 0                   $objects->{$ns}{ $uid->cache_key }
98                         = Elastic::Model::Deleted->new( uid => $uid );
99                 }
100 0               return;
101             }
102              
103             #===================================
104             sub DEMOLISH {
105             #===================================
106 0     0 0       my $self = shift;
107 0 0             return if in_global_destruction;
108 0               $self->model->detach_scope($self);
109             }
110              
111             1;
112              
113             =pod
114            
115             =encoding UTF-8
116            
117             =head1 NAME
118            
119             Elastic::Model::Scope - Keeps objects alive and connected
120            
121             =head1 VERSION
122            
123             version 0.52
124            
125             =head1 DESCRIPTION
126            
127             L<Elastic::Model::Scope> is an optional in-memory cache, which serves three
128             purposes:
129            
130             =over
131            
132             =item *
133            
134             Keep weak-ref L<Elastic::Doc> attributes alive
135            
136             =item *
137            
138             Reuse L<Elastic::Doc> objects as singletons.
139            
140             =item *
141            
142             Multiple scopes allow you to have multiple versions of L<Elastic::Doc> objects
143             live at the same time.
144            
145             =back
146            
147             See L<Elastic::Manual::Scoping> for a fuller discussion of when and how to use
148             scoping.
149            
150             =head1 ATTRIBUTES
151            
152             =head2 parent
153            
154             The parent scope of this scope, or UNDEF.
155            
156             =head1 METHODS
157            
158             The logic used in scopes is best explained by the examples below:
159            
160             =head2 get_object()
161            
162             $obj = $scope->get_object($domain_name, $uid);
163            
164             When calling L<Elastic::Model::Domain/"get()"> or L<Elastic::Model::Role::Model/"get_doc()">
165             to retrieve an object from Elasticsearch, we first check to see if we can
166             return the object from our in-memory cache by calling L</get_object()>:
167            
168             =head3 Getting an object that exists in the current scope
169            
170             If an object with the same C<namespace_name/type/id> exists in the CURRENT scope
171             (and its version is as least as high as the requested version, if any) then
172             we return the SAME object.
173            
174             $scope = $model->new_scope;
175             $one = $domain->get( user => 123 );
176             $two = $domain->get( user => 123 );
177            
178             print $one->name;
179             # Clint
180            
181             $two->name('John');
182            
183             print $one->name;
184             # John
185            
186             print refaddr($one) == refaddr($two) ? 'TRUE' : 'FALSE';
187             # TRUE
188            
189             =head3 Getting an object that exists in a parent scope
190            
191             If an object with the same C<domain_name/type/id> exists in the PARENT scope
192             (and its version is as least as high as the requested version, if any) then
193             we return a CLONE of the object. (Note: we clone the original object as it was
194             when loaded from Elasticsearch. Any unsaved changes are ignored.)
195            
196             $scope_1 = $model->new_scope;
197             $one = $domain->get( user => 123 );
198            
199             print $one->name;
200             # Clint
201            
202             $one->name('John');
203            
204             $scope_2 = $model->new_scope;
205             $two = $domain->get( user => 123 );
206            
207             print $two->name;
208             # Clint
209            
210             print refaddr($one) == refaddr($two) ? 'TRUE' : 'FALSE';
211             # FALSE
212            
213             Otherwise the calling method will fetch the object from Elasticsearch itself,
214             and store it in the current scope.
215            
216             =head3 Getting an object that has been deleted
217            
218             If the object exists in the same scope or a parent scope, but it is
219             an L<Elastic::Model::Deleted> object, then we return C<undef>.
220            
221             =head2 store_object()
222            
223             $object = $scope->store_object($ns_name, $object);
224            
225             When we load a object that doesn't exist in the current scope or in any of
226             its parents, or we create-a-new or update-an-existing object via
227             L<Elastic::Model::Role::Doc/"save()">,
228             we also store it in the current scope via L</store_object()>.
229            
230             $scope_1 = $model->new_scope;
231             $one = $domain->get( user => 123 );
232            
233             print $one->name;
234             # Clint
235            
236             $scope_2 = $model->new_scope;
237             $two = $domain->get( user => 123 );
238            
239             print $two->name;
240             # Clint
241            
242             print refaddr($one) == refaddr($two) ? 'TRUE' : 'FALSE';
243             # FALSE
244            
245             =head3 Storing an object in a new scope
246            
247             Now we update the C<$one> object, while B<< C<$scope_2> >> is current, and save it:
248            
249             $one->name('John');
250             $one->save;
251            
252             Object C<$one> is now in C<$scope_1> AND C<$scope_2>.
253            
254             $three = $domain->get( user => 123 );
255            
256             print $three->name;
257             # John
258            
259             print refaddr($one) == refaddr($three) ? 'TRUE' : 'FALSE';
260             # TRUE
261            
262             Object C<$two> still exists, and is still kept alive, but will no longer be
263             returned from C<$scope_2>.
264            
265             print $two->name;
266             # Clint
267            
268             =head2 delete_object()
269            
270             $scope->delete_object( $ns_name, $uid );
271            
272             When calling L<Elastic::Model::Role::Model/delete_doc()>,
273             L<Elastic::Model::Domain/delete_doc()> or L<Elastic::Model::Role::Doc/delete()>
274             we check to see if an object with the same UID (C<namespace_name/type/id>)
275             exists in the current scope.
276            
277             If it does, we rebless it into L<Elastic::Model::Deleted>. Otherwise, we
278             create a new L<Elastic::Model::Deleted> object with the C<$uid> and store
279             that in the current scope.
280            
281             =head3 Deleting an object which exists in the current scope
282            
283             $scope_1 = $model->new_scope;
284             $one = $domain->get( user => 1 );
285            
286             $domain->delete (user => 1 );
287            
288             print $domain->isa('Elastic::Model::Deleted') ? 'TRUE' : 'FALSE';
289             # TRUE
290            
291             print $one->name; # Throws an error,
292            
293             =head3 Deleting an object which doesn't exist in the current scope
294            
295             $scope_1 = $model->new_scope;
296             $one = $domain->get( user => 1 );
297            
298             $scope_2 = $model->new_scope;
299            
300             $domain->delete( user => 1);
301            
302             $two = $domain->get( user => 1 ); # Throws an error
303            
304             print $one->name;
305             # Clint
306            
307             undef $scope_2;
308             $two = $domain->get( user => 1 );
309            
310             print refaddr($one) == refaddr($two) ? 'TRUE' : 'FALSE';
311             # TRUE
312            
313             But, calling L<delete()|Elastic::Model::Role::Doc/delete()> on an object
314             which isn't in the current scope still affects that object:
315            
316             $scope_1 = $model->new_scope;
317             $one = $domain->get( user => 1 );
318            
319             $scope_2 = $model->new_scope;
320            
321             $one->delete;
322            
323             print $one->name; # Throws an error
324            
325             =head1 AUTHOR
326            
327             Clinton Gormley <drtech@cpan.org>
328            
329             =head1 COPYRIGHT AND LICENSE
330            
331             This software is copyright (c) 2015 by Clinton Gormley.
332            
333             This is free software; you can redistribute it and/or modify it under
334             the same terms as the Perl 5 programming language system itself.
335            
336             =cut
337              
338             __END__
339            
340             # ABSTRACT: Keeps objects alive and connected
341            
342