Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added partial objects

Added include_paths() and exclude_paths() to View, to allow retrieving
partial documents in searches.  The UID of a partial document is
marked as is_partial, which prevents the document being saved.

Partial objects are intended as light-weight objects purely for displaying
results, where only a few fields are required.

Closes #6
  • Loading branch information...
commit 9541ad3d6c046eff2169a1225b7063baf556822e 1 parent 097e55a
@clintongormley authored
View
32 lib/Elastic/Model/Result.pm
@@ -73,6 +73,15 @@ has 'object' => (
builder => '_build_object'
);
+#===================================
+has 'partial' => (
+#===================================
+ is => 'ro',
+ does => 'Elastic::Model::Role::Doc',
+ lazy => 1,
+ builder => '_build_partial'
+);
+
no Moose;
#===================================
@@ -94,6 +103,16 @@ sub _build_object {
}
#===================================
+sub _build_partial {
+#===================================
+ my $self = shift;
+ $self->result->{_partial} ||= $self->model->new_partial_doc(
+ uid => Elastic::Model::UID->new_partial( $self->result ),
+ partial_source => $self->field('_partial_doc')
+ );
+}
+
+#===================================
sub highlight {
#===================================
my $self = shift;
@@ -155,6 +174,7 @@ __END__
$object = $result->object;
$uid = $result->uid;
+ $partial_obj = $result->partial;
\%all_highlights = $result->highlights;
@field_highlights = $result->highlight('field_name');
@@ -201,6 +221,18 @@ The L<uid|Elastic::Model::UID> of the doc. L<index|Elastic::Model::UID/index>,
L<type|Elastic::Model::UID/type>, L<id|Elastic::Model::UID/id>
and L<routing|Elastic::Model::UID/routing> are provided for convenience.
+
+=head2 partial
+
+ $partial_obj = $result->partial_object();
+
+If your objects are large, you may want to load only part of the object in your
+search results. You can specify which parts of the object to include or exclude
+using L<Elastic::Model::View/"include_paths / exclude_paths">.
+
+The partial objects returned by L</partial> function exactly as real objects,
+except that they cannot be saved.
+
=head2 highlights
=head2 highlight
View
34 lib/Elastic/Model/Results.pm
@@ -274,6 +274,13 @@ eg C<MyApp::User>
Sets the "short" accessors (eg L</next>, L</prev>) to return the raw result
returned by ElasticSearch.
+=head2 as_partials()
+
+ $results->as_partials()
+
+Sets the "short" accessors (eg L</next>, L</prev>) to return partial objects
+as specified by L<Elastic::Model::View/"include_paths / exclude_paths">.
+
=head1 ELEMENT ACCESSORS
All of the accessors below have 4 forms:
@@ -296,6 +303,11 @@ Element, eg C<next_element> which returns the raw hashref from ElasticSearch
=item *
+Partial Doc, eg C<next_partial> which returns a partial object as specified
+by L<Elastic::Model::View/"include_paths / exclude_paths">.
+
+=item *
+
Short, which can return any one of the above, depending on which
L<Wrapper|/WRAPPERS> is currently in effect.
@@ -316,7 +328,7 @@ Returns the first element, and resets the iterator so that a call
to L</next> will return the second element. If there is
no first element, it returns undef.
-Also C<first_result>, C<first_object>, C<first_element>
+Also C<first_result>, C<first_object>, C<first_element>, C<first_partial>
=head2 next
@@ -330,7 +342,7 @@ element, then it will work like this:
$results->next; # returns undef, and resets iterator
$results->next; # returns first element
-Also C<next_result>, C<next_object>, C<next_element>
+Also C<next_result>, C<next_object>, C<next_element>, C<next_partial>
=head2 prev
@@ -344,7 +356,7 @@ first element, then it will work like this:
$results->prev; # returns undef, and resets iterator to end
$results->prev; # returns last element
-Also C<prev_result>, C<prev_object>, C<prev_element>
+Also C<prev_result>, C<prev_object>, C<prev_element>, C<prev_partial>
=head2 current
@@ -352,7 +364,7 @@ Also C<prev_result>, C<prev_object>, C<prev_element>
Returns the current element, or undef
-Also C<current_result>, C<current_object>, C<current_element>
+Also C<current_result>, C<current_object>, C<current_element>, C<current_partial>
=head2 last
@@ -363,7 +375,7 @@ to L</next> will return undef, and a second call to
L</next> will return the first element If there is
no last element, it returns undef.
-Also C<last_result>, C<last_object>, C<last_element>
+Also C<last_result>, C<last_object>, C<last_element>, C<last_partial>
=head2 peek_next
@@ -371,7 +383,8 @@ Also C<last_result>, C<last_object>, C<last_element>
Returns the next element (or undef), but doesn't move the iterator.
-Also C<peek_next_result>, C<peek_next_object>, C<peek_next_element>
+Also C<peek_next_result>, C<peek_next_object>, C<peek_next_element>,
+C<peek_next_partial>
=head2 peek_prev
@@ -379,7 +392,8 @@ Also C<peek_next_result>, C<peek_next_object>, C<peek_next_element>
Returns the previous element (or undef), but doesn't move the iterator.
-Also C<peek_prev_result>, C<peek_prev_object>, C<peek_prev_element>
+Also C<peek_prev_result>, C<peek_prev_object>, C<peek_prev_element>,
+C<peek_prev_partial>
=head2 shift
@@ -388,7 +402,7 @@ Also C<peek_prev_result>, C<peek_prev_object>, C<peek_prev_element>
Returns the L</first> element and removes it from from the list. L</size>
will decrease by 1. Returns undef if there are no more elements.
-Also C<shift_result>, C<shift_object>, C<shift_element>
+Also C<shift_result>, C<shift_object>, C<shift_element>, C<shift_partial>
=head2 slice
@@ -408,7 +422,7 @@ If your iterator only contains 5 elements:
$results->slice(3,10); # elements 3..4
$results->slice(10,10); # an empty list
-Also C<slice_results>, C<slice_objects>, C<slice_elements>
+Also C<slice_results>, C<slice_objects>, C<slice_elements>, C<slice_partials>
=head2 all
@@ -416,5 +430,5 @@ Also C<slice_results>, C<slice_objects>, C<slice_elements>
Returns all L</elements> as a list.
-Also C<all_results>, C<all_objects>, C<all_elements>
+Also C<all_results>, C<all_objects>, C<all_elements>, C<all_partials>
View
44 lib/Elastic/Model/Role/Model.pm
@@ -348,6 +348,22 @@ sub get_doc_source {
}
#===================================
+sub new_partial_doc {
+#===================================
+ my ( $self, %args ) = @_;
+ my $uid = $args{uid}
+ or croak "No UID passed to new_partial_doc()";
+
+ my $source = $args{partial_source}
+ or croak "No (partial_source) passed to new_partial_doc()";
+
+ my $ns = $self->namespace_for_domain( $uid->index );
+
+ my $class = $ns->class_for_type( $uid->type );
+ return $class->meta->new_stub( $uid, $source );
+}
+
+#===================================
sub doc_exists {
#===================================
my ( $self, %args ) = @_;
@@ -361,8 +377,15 @@ sub save_doc {
#===================================
my ( $self, %args ) = @_;
- my $doc = delete $args{doc};
- my $uid = $doc->uid;
+ my $doc = delete $args{doc};
+ my $uid = $doc->uid;
+
+ croak "Cannot save partial doc type ("
+ . $uid->type
+ . ") id ("
+ . $uid->id . ")"
+ if $uid->is_partial;
+
my $data = $self->deflate_object($doc);
my $action
@@ -457,7 +480,7 @@ sub _handle_error {
die $error
unless $on_conflict
- and $error =~ /ElasticSearch::Error::Conflict/;
+ and $error =~ /ElasticSearch::Error::Conflict/;
my $new;
if ( my $current_version = $error->{-vars}{current_version} ) {
@@ -779,6 +802,21 @@ method.
Passes C<%args> through to L<Elastic::Model::Store/"search()">
+=head3 new_partial_doc()
+
+ part_doc = $model->new_partial_doc(
+ uid => $uid,
+ partial_source => \%source
+ );
+
+Creates an instance of a partial doc (ie an object which contains only some of
+the values stored in elasticsearch). These partial docs are useful when
+your objects are large, and you need to display search results which
+require only a few attributes, instead of the whole object.
+
+Attempting to save a partial doc will cause an error to be thrown.
+
+You shouldn't need to call this method yourself.
=head2 Miscellaneous
View
123 lib/Elastic/Model/Role/Results.pm
@@ -78,6 +78,24 @@ has '_as_objects' => (
builder => '_as_objects_builder'
);
+#===================================
+has '_as_partial' => (
+#===================================
+ isa => CodeRef,
+ is => 'ro',
+ lazy => 1,
+ builder => '_as_partial_builder'
+);
+
+#===================================
+has '_as_partials' => (
+#===================================
+ isa => CodeRef,
+ is => 'ro',
+ lazy => 1,
+ builder => '_as_partials_builder'
+);
+
no Moose;
#===================================
@@ -108,8 +126,7 @@ sub _as_object_builder {
$raw->{_object} ||= do {
my $uid = Elastic::Model::UID->new_from_store($raw);
$model->get_doc( uid => $uid, source => $raw->{_source} );
- }
-
+ };
};
}
@@ -123,6 +140,42 @@ sub _as_objects_builder {
$_->{_object} ||= do {
my $uid = Elastic::Model::UID->new_from_store($_);
$m->get_doc( uid => $uid, source => $_->{_source} );
+ };
+ } @_;
+ };
+}
+
+#===================================
+sub _as_partial_builder {
+#===================================
+ my $self = shift;
+ my $model = $self->model;
+ sub {
+ my $raw = shift or return;
+ $raw->{_partial} ||= do {
+ my $uid = Elastic::Model::UID->new_partial($raw);
+ $model->new_partial_doc(
+ uid => $uid,
+ partial_source => $raw->{fields}->{_partial_doc}
+ );
+ }
+
+ };
+}
+
+#===================================
+sub _as_partials_builder {
+#===================================
+ my $self = shift;
+ my $m = $self->model;
+ sub {
+ map {
+ $_->{_partial} ||= do {
+ my $uid = Elastic::Model::UID->new_partial($_);
+ $m->new_partial_doc(
+ uid => $uid,
+ partial_source => $_->{fields}->{_partial_doc}
+ );
}
} @_;
};
@@ -147,6 +200,15 @@ sub as_objects {
}
#===================================
+sub as_partials {
+#===================================
+ my $self = shift;
+ $self->wrapper( $self->_as_partial );
+ $self->multi_wrapper( $self->_as_partials );
+ $self;
+}
+
+#===================================
sub first_result { $_[0]->_as_result->( $_[0]->first_element ) }
sub last_result { $_[0]->_as_result->( $_[0]->last_element ) }
sub next_result { $_[0]->_as_result->( $_[0]->next_element ) }
@@ -184,6 +246,25 @@ sub slice_objects {
$self->_as_objects->( $self->slice_elements(@_) );
}
+#===================================
+sub first_partial { $_[0]->_as_partial->( $_[0]->first_element ) }
+sub last_partial { $_[0]->_as_partial->( $_[0]->last_element ) }
+sub next_partial { $_[0]->_as_partial->( $_[0]->next_element ) }
+sub prev_partial { $_[0]->_as_partial->( $_[0]->prev_element ) }
+sub current_partial { $_[0]->_as_partial->( $_[0]->current_element ) }
+sub peek_next_partial { $_[0]->_as_partial->( $_[0]->peek_next_element ) }
+sub peek_prev_partial { $_[0]->_as_partial->( $_[0]->peek_prev_element ) }
+sub shift_partial { $_[0]->_as_partial->( $_[0]->shift_element ) }
+sub all_partials { $_[0]->_as_partials->( $_[0]->all_elements ) }
+#===================================
+
+#===================================
+sub slice_partials {
+#===================================
+ my $self = shift;
+ $self->_as_partials->( $self->slice_elements(@_) );
+}
+
1;
__END__
@@ -265,6 +346,13 @@ Sets the "short" accessors (eg L<Elastic::Model::Role::Iterator/next> or
L<Elastic::Model::Role::Iterator/prev>) to return the object itself,
eg C<MyApp::User>
+=head2 as_partials()
+
+ $results->as_partials()
+
+Sets the "short" accessors (eg L</next>, L</prev>) to return partial objects
+as specified by L<Elastic::Model::View/"include_paths / exclude_paths">.
+
=head1 RESULT ACCESSORS
Each of the methods listed below takes the result of the related
@@ -296,7 +384,7 @@ in an L<Elastic::Model::Result> object. For instance:
=head1 OBJECT ACCESSORS
Each of the methods listed below takes the result of the related
-C<_element> accessor in L<Elastic::Model::Role::Iterator> and inflate the
+C<_element> accessor in L<Elastic::Model::Role::Iterator> and inflates the
related object (eg a C<MyApp::User> object). For instance:
$object = $results->next_object;
@@ -320,3 +408,32 @@ related object (eg a C<MyApp::User> object). For instance:
=head2 all_objects
=head2 slice_objects
+
+=head1 PARTIAL OBJECT ACCESSORS
+
+Each of the methods listed below takes the result of the related
+C<_element> accessor in L<Elastic::Model::Role::Iterator> and inflates the
+related partial object as specified by
+L<Elastic::Model::View/"include_paths / exclude_paths". For instance:
+
+ $object = $results->next_partial;
+
+=head2 first_partial
+
+=head2 last_partial
+
+=head2 next_partial
+
+=head2 prev_partial
+
+=head2 current_partial
+
+=head2 peek_next_partial
+
+=head2 peek_prev_partial
+
+=head2 shift_partial
+
+=head2 all_partials
+
+=head2 slice_partials
View
38 lib/Elastic/Model/UID.pm
@@ -54,6 +54,13 @@ has from_store => (
);
#===================================
+has 'is_partial' => (
+#===================================
+ is => 'ro',
+ isa => Bool,
+);
+
+#===================================
has cache_key => (
#===================================
is => 'ro',
@@ -78,6 +85,19 @@ sub new_from_store {
}
#===================================
+sub new_partial {
+#===================================
+ my $class = shift;
+ my %params = %{ shift() };
+ $class->new(
+ from_store => 1,
+ is_partial => 1,
+ routing => $params{fields}{routing},
+ map { $_ => $params{"_$_"} } qw(index type id version)
+ );
+}
+
+#===================================
sub update_from_store {
#===================================
my $self = shift;
@@ -211,6 +231,11 @@ an older version of the document will throw an exception.
A boolean value indicating whether the C<UID> was loaded from ElasticSearch
(C<true>) or created via L</"new()">.
+=head2 is_partial
+
+A boolean value indicating whether the C<UID> represents a partial or full
+object. Partial objects cannot be saved.
+
=head2 cache_key
A generated string combining the L</"type"> and the L</"id">
@@ -241,6 +266,19 @@ Creates a new UID with L</"from_store"> set to false.
This is called when creating a new UID for a doc that has been loaded
from ElasticSearch. You shouldn't need to use this method directly.
+=head2 new_partial()
+
+ $uid = Elastic::Model::UID->new_partial(
+ _index => $index,
+ _type => $type,
+ _id => $id,
+ _version => $version,
+ fields => { routing => $routing }
+ );
+
+This is called when creating a new UID for a partial doc that has been loaded
+from ElasticSearch. You shouldn't need to use this method directly.
+
=head2 clone()
$new_uid = $uid->clone();
View
59 lib/Elastic/Model/View.pm
@@ -69,7 +69,8 @@ has 'fields' => (
isa => ArrayRefOfStr,
coerce => 1,
is => 'rw',
- default => sub { ['_source'] },
+ lazy => 1,
+ builder => '_build_fields',
);
#===================================
@@ -150,6 +151,22 @@ has 'routing' => (
);
#===================================
+has 'include_paths' => (
+#===================================
+ is => 'rw',
+ isa => ArrayRef [Str],
+ predicate => '_has_include_paths'
+);
+
+#===================================
+has 'exclude_paths' => (
+#===================================
+ is => 'rw',
+ isa => ArrayRef [Str],
+ predicate => '_has_exclude_paths'
+);
+
+#===================================
has 'script_fields' => (
#===================================
isa => HashRef,
@@ -256,7 +273,8 @@ around [
around [
#===================================
- 'domain', 'type', 'fields', 'sort', 'routing', 'stats'
+ 'domain', 'type', 'fields', 'sort', 'routing', 'stats',
+ 'include_paths', 'exclude_paths'
#===================================
] => sub { _clone_args( \&_array_args, @_ ) };
@@ -315,6 +333,14 @@ sub _check_no_fields {
if $val->{fields};
}
+#===================================
+sub _build_fields {
+#===================================
+ my $self = shift;
+ return $self->_has_include_paths
+ || $self->_has_exclude_paths ? [] : ['_source'];
+}
+
no Moose;
#===================================
@@ -391,6 +417,13 @@ sub _build_search {
$highlight = { %{ $self->highlighting || {} }, fields => $fields };
}
+ my %partial;
+
+ $partial{include} = $self->include_paths
+ if $self->_has_include_paths;
+ $partial{exclude} = $self->exclude_paths
+ if $self->_has_exclude_paths;
+
my %args = ( (
map { $_ => $self->$_ }
qw(
@@ -407,7 +440,7 @@ sub _build_search {
@_,
version => 1,
fields => [ '_parent', '_routing', @{ $self->fields } ],
-
+ ( partial_fields => { _partial_doc => \%partial } ) x !!%partial
);
return { map { $_ => $args{$_} } grep { defined $args{$_} } keys %args };
@@ -810,6 +843,26 @@ L<Script fields|http://www.elasticsearch.org/guide/reference/api/search/script-f
can be generated using the L<mvel|http://mvel.codehaus.org/Language+Guide+for+2.0>
scripting language. (You can also use L<Javascript, Python and Java|http://www.elasticsearch.org/guide/reference/modules/scripting>.)
+=head2 include_paths / exclude_paths
+
+ $new_view = $view->include_paths('foo.*')
+ ->exclude_paths('foo.bar.*','baz.*');
+
+ $results = $new_view->search->as_partials;
+ $partial_obj = $results->next;
+
+If your objects are large, you may want to load only part of the object in your
+search results. You can specify which parts of the object to include or exclude
+using C<include_paths> and C<exclude_paths>. If either of these is set
+then the full C<_source> field will not be loaded (unless you specify it
+explicitly using L</fields>).
+
+The partial objects returned when L<Elastic::Model::Results/as_partials()>
+is in effect function exactly as real objects, except that they cannot
+be saved.
+
+See C<Partial fields> on L<http://www.elasticsearch.org/guide/reference/api/search/fields.html>.
+
=head2 routing
$new_view = $view->routing( 'routing_val' );
View
30 t/40_view/01_view_attrs.t
@@ -608,6 +608,36 @@ test_view(
{ script_fields => { two => { script => 'yy' } } }
);
+test_view(
+ 'Include paths',
+ $view->include_paths('foo.*'),
+ { partial_fields => { _partial_doc => { include => ['foo.*'] } },
+ fields => [ "_parent", "_routing" ],
+ }
+);
+
+test_view(
+ 'Exclude paths',
+ $view->exclude_paths('foo.*'),
+ { partial_fields => { _partial_doc => { exclude => ['foo.*'] } },
+ fields => [ "_parent", "_routing" ],
+ }
+);
+
+test_view(
+ 'Include and exclude paths',
+ $view->include_paths( 'foo.*', 'fuz.*' )
+ ->exclude_paths( 'bar.*', 'baz.*' ),
+ { partial_fields => {
+ _partial_doc => {
+ include => [ 'foo.*', 'fuz.*' ],
+ exclude => [ 'bar.*', 'baz.*' ]
+ }
+ },
+ fields => [ "_parent", "_routing" ],
+ }
+);
+
## timeout ##
test_view(
'New-timeout',
View
16 t/40_view/03_results.t
@@ -18,7 +18,9 @@ ok my $ns = $model->namespace('myapp'), 'Got ns';
create_users($model);
-isa_ok my $view = $model->view( domain => 'myapp' )->sort('name.untouched'),
+isa_ok my $view
+ = $model->view( domain => 'myapp' )->sort('name.untouched')
+ ->fields('_source')->include_paths('name'),
'Elastic::Model::View', 'View';
isa_ok my $it = $view->search, 'Elastic::Model::Results', 'Iterator';
@@ -240,6 +242,14 @@ sub test_single {
isa_ok $r= $it->$name, 'Elastic::Model::Result',
"$desc $name as results - $id";
is $r->id, $id, "$desc $name as results ID - $id";
+ $reset->();
+
+ $it->as_partials;
+ isa_ok $r= $it->$name, 'MyApp::User',
+ "$desc $name as partials - $id";
+ is $r->uid->is_partial, 1, "$desc $name as partials is partial";
+ ok $r->name, "$desc $name as partials has name";
+ ok !$r->{timestamp}, "$desc $name as partials has no timestamp";
}
else {
@@ -262,6 +272,10 @@ sub test_single {
$it->as_results;
is $r= $it->$name, undef, "$desc $name as results - undef";
+ $reset->();
+
+ $it->as_partials;
+ is $r= $it->$name, undef, "$desc $name as partials - undef";
}
View
16 t/40_view/05_result.t
@@ -122,6 +122,22 @@ is
like $result->explain, qr/product of:/, 'AdvResult->explain';
+# Partial docs #
+
+isa_ok $result = $view->queryb( { name => 'Aardwolf' } ) #
+ ->include_paths('name') #
+ ->first(), 'Elastic::Model::Result', 'Partial';
+
+isa_ok my $doc = $result->partial, 'MyApp::User', 'Partial->partial';
+ok $doc->{name}, 'Partial has name';
+ok !$doc->{timestamp}, 'Partial has no timestamp';
+ok $doc->uid->is_partial, 'Partial UID is partial';
+
+isa_ok $doc = $result->object, 'MyApp::User', 'Partial->object';
+ok $doc->{name}, 'Object has name';
+ok $doc->{timestamp}, 'Object has timestamp';
+ok !$doc->uid->is_partial, 'Object UID is not partial';
+
done_testing;
__END__
Please sign in to comment.
Something went wrong with that request. Please try again.