File Coverage

blib/lib/ElasticSearchX/Model/Document/Trait/Class.pm
Criterion Covered Total %
statement 79 108 73.1
branch 18 46 39.1
condition 6 22 27.2
subroutine 19 23 82.6
pod 7 10 70.0
total 129 209 61.7


line stmt bran cond sub pod time code
1             #
2             # This file is part of ElasticSearchX-Model
3             #
4             # This software is Copyright (c) 2016 by Moritz Onken.
5             #
6             # This is free software, licensed under:
7             #
8             # The (three-clause) BSD License
9             #
10             package ElasticSearchX::Model::Document::Trait::Class;
11             $ElasticSearchX::Model::Document::Trait::Class::VERSION = '1.0.2';
12             # ABSTRACT: Trait that extends the meta class of a document class
13 7     7   2467 use Moose::Role;
  7         17350  
  7         24  
14 7     7   24256 use Carp;
  7         37  
  7         362  
15 7     7   27 use List::Util ();
  7         9  
  7         82  
16 7     7   3269 use Module::Find ();
  7         6421  
  7         121  
17 7     7   27 use Class::Load ();
  7         7  
  7         122  
18 7     7   20 use Eval::Closure;
  7         7  
  7         7676  
19              
20             has set_class => ( is => 'ro', builder => '_build_set_class', lazy => 1 );
21             has short_name => ( is => 'ro', builder => '_build_short_name', lazy => 1 );
22             has _all_properties =>
23             ( is => 'ro', lazy => 1, builder => '_build_all_properties' );
24             has _isa_arrayref =>
25             ( is => 'ro', lazy => 1, builder => '_build_isa_arrayref' );
26              
27             has _field_alias => (
28             is => 'ro',
29             traits => ['Hash'],
30             isa => 'HashRef[Str]',
31             default => sub { {} },
32             handles => { _add_field_alias => 'set' },
33             );
34             has _reverse_field_alias => (
35             is => 'ro',
36             traits => ['Hash'],
37             isa => 'HashRef[Str]',
38             default => sub { {} },
39             handles => { _add_reverse_field_alias => 'set' },
40             );
41             has _id_attribute => ( is => 'rw', lazy_build => 1 );
42              
43             has _attribute_traits => ( is => 'ro', lazy_build => 1 );
44              
45             sub _build__attribute_traits {
46             return {
47             map {
48 6     6   29 Class::Load::load_class($_);
  24         12734  
49 24         701 my ($name) = ( $_ =~ /::(\w+)$/ );
50 24         259 lc($name) => $_
51             } Module::Find::findallmod(
52             'ElasticSearchX::Model::Document::Trait::Field')
53             };
54             }
55              
56             sub _build_set_class {
57 2     2   2 my $self = shift;
58 2         10 my $set = $self->name . '::Set';
59 2 50 50     5 eval { Class::Load::load_class($set); } and return $set
  2         6  
60             or return 'ElasticSearchX::Model::Document::Set';
61             }
62              
63             sub mapping {
64 5     5 1 4717 my $self = shift;
65 5         10 my $props = { map { $_->mapping } $self->get_all_properties };
  25         1057  
66 5         17 my $parent = $self->get_parent_attribute;
67             return {
68             $parent ? ( _parent => { type => $parent->name } ) : (),
69             dynamic => \0,
70             properties => $props,
71 5 100       23 map { $_->type_mapping } $self->get_all_properties,
  25         1466  
72             };
73             }
74              
75             sub _build_short_name {
76 11     11   11 my $self = shift;
77 11         61 ( my $name = $self->name ) =~ s/^.*:://;
78 11         326 return lc($name);
79             }
80              
81             sub get_id_attribute {
82 4     4 1 2374 return shift->_id_attribute;
83             }
84              
85             sub _build__id_attribute {
86 1     1   2 my $self = shift;
87             my (@id)
88 1         4 = grep { $_->does('ElasticSearchX::Model::Document::Trait::Field::ID') }
  3         1530  
89             $self->get_all_properties;
90 1         668 return pop @id;
91             }
92              
93             sub get_parent_attribute {
94 6     6 1 18 my $self = shift;
95 6         13 my ( $id, $more ) = grep { $_->parent } $self->get_all_properties;
  28         2241  
96 6 50       14 croak "Cannot have more than one parent field on a class" if ($more);
97 6         11 return $id;
98             }
99              
100             sub get_version_attribute {
101 0     0 0 0 shift->get_attribute('_version');
102             }
103              
104             sub add_property {
105 24     24 1 47 my ( $self, $name ) = ( shift, shift );
106 24 50       73 Moose->throw_error('Usage: has \'name\' => ( key => value, ... )')
107             if @_ % 2 == 1;
108 24         57 my %options = ( definition_context => _caller_info(), @_ );
109 24   50     123 $options{traits} ||= [];
110             push(
111 23         56 @{ $options{traits} },
112             'ElasticSearchX::Model::Document::Trait::Attribute'
113 24 100 66     131 ) if ( $options{property} || !exists $options{property} );
114 24         38 delete $options{property};
115 24         882 my $attr_traits = $self->_attribute_traits;
116 24         69 for ( grep { $attr_traits->{$_} } keys %options ) {
  104         137  
117 1         1 push( @{ $options{traits} }, $attr_traits->{$_} );
  1         3  
118              
119             #(my $class_trait = $attr_traits{$_}) =~ s/::Field::/::Class::/;
120             #Moose::Util::apply_all_roles($meta, $class_trait);
121             }
122 24 100       86 my $attrs = ( ref($name) eq 'ARRAY' ) ? $name : [ ($name) ];
123 24         139 $self->add_attribute( $_, %options ) for @$attrs;
124             }
125              
126             sub _caller_info {
127 24 50   24   50 my $level = @_ ? ( $_[0] + 1 ) : 2;
128 24         29 my %info;
129 24         227 @info{qw(package file line)} = caller($level);
130 24         107 return \%info;
131             }
132              
133             sub all_properties_loaded {
134 0     0 1 0 my ( $self, $instance ) = @_;
135 0         0 my $loaded = $instance->_loaded_attributes;
136 0 0       0 return 1 unless ($loaded);
137 0         0 my @properties = $self->get_all_properties;
138 0         0 for (@properties) {
139             return undef
140 0 0 0     0 unless ( $loaded->{ $_->name } || $_->has_value($instance) );
141             }
142 0         0 return 1;
143             }
144              
145             sub get_all_properties {
146 24     24 1 87 my $self = shift;
147 24 100       71 return @{ $self->_all_properties }
  4         235  
148             if ( $self->is_immutable );
149 20         58 return @{ $self->_build_all_properties };
  20         34  
150             }
151              
152             sub _build_all_properties {
153             return [
154 22     22   50 grep { $_->does('ElasticSearchX::Model::Document::Trait::Attribute') }
  179         42296  
155             shift->get_all_attributes
156             ];
157             }
158              
159             sub _build_isa_arrayref {
160 0         0 return { map { $_->name => $_->isa_arrayref }
161 0     0   0 @{ shift->_all_properties } };
  0         0  
162             }
163              
164             sub get_data {
165 5     5 1 425 my ( $self, $instance ) = @_;
166             return {
167             map {
168 7 100 66     304 my $deflate
169             = $_->is_inflated($instance)
170             || $_->is_required && !$_->has_value($instance)
171             ? $_->deflate($instance)
172             : $_->get_raw_value($instance);
173 7 100       774 defined $deflate ? ( $_->field_name => $deflate ) : ();
174 26 100       934 } grep { $_->has_value($instance) || $_->is_required }
175 5         21 grep { $_->property } $self->get_all_properties
  36         1518  
176             };
177             }
178              
179             sub get_query_data {
180 1     1 0 14 my ( $self, $instance ) = @_;
181             return {
182             map {
183 0 0 0     0 my $deflate
184             = $_->is_inflated($instance)
185             || $_->is_required && !$_->has_value($instance)
186             ? $_->deflate($instance)
187             : $_->get_raw_value($instance);
188 0         0 ( my $field = $_->field_name ) =~ s/^_//;
189 0 0       0 defined $deflate ? ( $field => $deflate ) : ();
190 0 0       0 } grep { $_->has_value($instance) || $_->is_required }
191 1         4 grep { $_->query_property } $self->get_all_properties
  3         153  
192             };
193             }
194              
195             sub inflate_result {
196 0     0 0   my ( $self, $index, $res ) = @_;
197              
198             #my $id = $self->get_id_attribute;
199 0           my $parent = $self->get_parent_attribute;
200 0           my $arrays = $self->_isa_arrayref;
201 0 0         my $fields = { %{ $res->{_source} || {} }, %{ $res->{fields} || {} } };
  0 0          
  0            
202             $fields = {
203             map {
204 0           my $is_array = ref $fields->{$_} eq "ARRAY";
  0            
205             $arrays->{$_} && !$is_array ? ( $_ => [ $fields->{$_} ] )
206             : !$arrays->{$_} && $is_array ? ( $_ => $fields->{$_}->[0] )
207 0 0 0       : ( $_ => $fields->{$_} );
    0 0        
208             } keys %$fields
209             };
210 0           my $map = $self->_reverse_field_alias;
211             map {
212             $fields->{ $map->{$_} }
213             = defined $res->{$_}
214             ? $res->{$_}
215 0 0         : $fields->{$_}
216             }
217 0 0         grep { defined $fields->{$_} || defined $res->{$_} } keys %$map;
  0            
218             return $self->name->new(
219             {
220             %$fields,
221             index => $index,
222             _id => $res->{_id},
223             _version => $res->{_version},
224 0 0         $parent ? ( $parent->name => $res->{_parent} ) : (),
225             }
226             );
227             }
228              
229             1;
230              
231             __END__
232              
233             =pod
234              
235             =encoding UTF-8
236              
237             =head1 NAME
238              
239             ElasticSearchX::Model::Document::Trait::Class - Trait that extends the meta class of a document class
240              
241             =head1 VERSION
242              
243             version 1.0.2
244              
245             =head1 ATTRIBUTES
246              
247             =head2 set_class
248              
249             A call to C<< $index->type('tweet') >> returns an instance of C<set_class>. Given a
250             document class C<MyModel::Tweet>, the builder of this attribute tries to find a
251             class named C<MyModel::Tweet::Set>. If it's not found, the default class
252             L<ElasticSearchX::Model::Document::Set> is used.
253              
254             A custum set class (e.g. C<MyModel::Tweet::Set>) B<must> inherit from
255             L<ElasticSearchX::Model::Document::Set>.
256              
257             =head2 short_name
258              
259             MyClass::Tweet->meta->short_name; # tweet
260              
261             The C<short_name> is used as name for the type. It defaults to the lowercased,
262             last segment of the class name.
263              
264             =head1 METHODS
265              
266             =head2 mapping
267              
268             my $mapping = $document->meta->mapping;
269              
270             Builds the type mapping for this document class. It loads all properties
271             using L</get_all_properties> and calls
272             L<ElasticSearchX::Model::Document::Trait::Attribute/build_property>.
273              
274             =head2 add_property
275              
276             $meta->add_property( name => ( is => 'ro', isa => 'Str' ) )
277              
278             Add a property through the C<$meta> object.
279              
280             =head2 all_properties_loaded
281              
282             Returns true if all properties were acutally loaded from storage
283             or whether C<ElasticSearchX::Model::Set/fields> was used to return
284             a partial result set.
285              
286             =head2 get_id_attribute
287              
288             Get the C<id> attribute, i.e. the attribute that has the C<id> option
289             set. Returns undef if it doesn't exist.
290              
291             =head2 get_parent_attribute
292              
293             Get the C<parent> attribute, i.e. the attribute that has the C<parent> option
294             set. Returns undef if it doesn't exist.
295              
296             =head2 get_all_properties
297              
298             Returns a list of all properties in the document class. An attribute is considered
299             a property, if it I<does> the L<ElasticSearchX::Model::Document::Trait::Attribute>
300             role. That means all attributes that don't have the C<property> option explicitly
301             set to C<0>.
302              
303             Since this method is called quite often, the result is cached if the document class
304             is immutable.
305              
306             =head2 get_data
307              
308             L<ElasticSearchX::Model::Document/put> calls this method to get an HashRef of
309             all properties and their values. Values are deflated if a deflator was specified
310             (e.g. L<DateTime> objects are deflated to an ISO8601 string).
311              
312             =head1 AUTHOR
313              
314             Moritz Onken
315              
316             =head1 COPYRIGHT AND LICENSE
317              
318             This software is Copyright (c) 2016 by Moritz Onken.
319              
320             This is free software, licensed under:
321              
322             The (three-clause) BSD License
323              
324             =cut