Skip to content

Commit

Permalink
Added Elastic::Model::SearchBuilder to handle easy serialization of $…
Browse files Browse the repository at this point in the history
…docs or $uids into search params
  • Loading branch information
clintongormley committed Aug 2, 2012
1 parent 179116e commit 79e9aa3
Show file tree
Hide file tree
Showing 13 changed files with 5,506 additions and 6 deletions.
245 changes: 245 additions & 0 deletions lib/Elastic/Model/SearchBuilder.pm
@@ -0,0 +1,245 @@
package Elastic::Model::SearchBuilder;

use strict;
use warnings;
use parent 'ElasticSearch::SearchBuilder';
use Carp;

#===================================
sub _top_ElasticDocREF {
#===================================
my ( $self, $type, $doc ) = @_;
$self->_uid_to_terms( $type, '', $doc->uid );
}

#===================================
sub _top_ElasticUIDREF {
#===================================
my ( $self, $type, $uid ) = @_;
$self->_uid_to_terms( $type, '', $uid );
}

#===================================
sub _hashpair_ElasticDocREF {
#===================================
my ( $self, $type, $k, $v ) = @_;
$self->_uid_to_terms( $type, $k, $v->uid );
}

#===================================
sub _hashpair_ElasticUIDREF {
#===================================
my ( $self, $type, $k, $v ) = @_;
$self->_uid_to_terms( $type, $k, $v );
}

#===================================
sub _filter_field_terms {
#===================================
my ( $self, $k, $op, $val ) = @_;

return $self->_SWITCH_refkind(
"Filter field operator -$op",
$val,
{ ElasticDocREF =>
sub { $self->_uid_to_terms( 'filter', $k, $val->uid ) },
ElasticUIDREF =>
sub { $self->_uid_to_terms( 'filter', $k, $val ) },
FALLBACK => sub {
$self->SUPER::_filter_field_terms( $k, $op, $val );
},
}
);
}

#===================================
sub _query_field_text {
#===================================
my $self = shift;
my ( $k, $op, $val ) = @_;

return $self->_SWITCH_refkind(
"Query field operator -$op",
$val,
{ ElasticDocREF =>
sub { $self->_uid_to_terms( 'query', $k, $val->uid ) },
ElasticUIDREF =>
sub { $self->_uid_to_terms( 'query', $k, $val ) },
FALLBACK => sub {
$self->SUPER::_query_field_text( $k, $op, $val );
},
}
);
}

#===================================
sub _uid_to_terms {
#===================================
my ( $self, $type, $k, $uid ) = @_;
$k = length $k ? $k . '.' : '';
my @clauses;
for (qw(index type id)) {
my $val = $uid->$_ or croak "UID missing ($_)";
push @clauses, { term => { "${k}uid.$_" => $val } };
}
return $type eq 'query'
? { bool => { must => \@clauses } }
: { and => \@clauses }

}

#===================================
sub _refkind {
#===================================
my ( $self, $data ) = @_;

return 'UNDEF' unless defined $data;

my $ref
= !Scalar::Util::blessed($data) ? ref $data
: !$data->can('does') ? ''
: $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc'
: $data->isa('Elastic::Model::UID') ? 'ElasticUID'
: '';

return 'SCALAR' unless $ref;

my $n_steps = 1;
while ( $ref eq 'REF' ) {
$data = $$data;
$ref
= !Scalar::Util::blessed($data) ? ref $data
: !$data->can('does') ? ''
: $data->does('Elastic::Model::Role::Doc') ? 'ElasticDoc'
: $data->isa('Elastic::Model::UID') ? 'ElasticUID'
: '';
$n_steps++ if $ref;
}

return ( $ref || 'SCALAR' ) . ( 'REF' x $n_steps );
}

1;

# ABSTRACT: An Elastic::Model specific subclass of L<ElasticSearch::SearchBuilder>

=head1 DESCRIPTION
L<Elastic::Model::SearchBuilder> is a sub-class of L<ElasticSearch::SearchBuilder>
to add automatic handling of L<Elastic::Doc> and L<Elastic::Model::UID>
values.
This document just explains the functionality that
L<Elastic::Model::SearchBuilder> adds.
B<For the full SearchBuilder docs, see L<ElasticSearch::SearchBuilder>>.
=head1 THE PROBLEM
Consider this class (where C<MyApp::User> is also an
L<Elastic::Doc> class):
package MyApp::Comment;
use Elastic::Doc;
has 'user' => (
is => 'ro',
isa => 'MyApp::User,
);
has 'text' => (
is => 'ro',
isa => 'Str',
);
We can create a comment as follows:
$domain->create(
comment => {
text => 'I like Elastic::Model',
user => $user,
}
);
The C<comment> object would be stored in ElasticSearch as something like this:
{
text => "I like Elastic::Model",
user => {
uid => {
index => 'myapp',
type => 'user',
id => 'abcdefg',
},
.... any other user fields....
}
}
In order to search for any comments by user C<$user>, you would need to do this:
$view->type('comment')
->filterb(
'user.uid.index' => $user->uid->index,
'user.uid.type' => $user->uid->type,
'user.uid.id' => $user->uid->id,
)
->search;
=head1 THE SOLUTION
With L<Elastic::Model::SearchBuilder>, you can do it as follows:
$view->type('comment')
->filterb( user => $user )
->search;
Or with the C<UID>:
$view->type('comment')
->filterb( user => $user->uid )
->search;
=head1 FURTHER EXAMPLES
=head2 Query or Filter
This works for both queries and filters, eg:
$view->queryb ( user => $user )->search;
$view->filterb( user => $user )->search;
=head2 Doc or UID
You can use either the doc/object itself, or an L<Elastic::Model::UID> object:
$uid = $user->uid;
$view->queryb ( user => $uid )->search;
$view->filterb( user => $uid )->search;
=head2 Negating queries:
$view->queryb ( user => { '!=' => $user })->search;
$view->filterb( user => { '!=' => $user })->search;
=head2 "IN" queries
$view->queryb ( user => \@users )->search;
$view->filterb( user => \@users )->search;
=head2 "NOT IN" queries
$view->queryb ( user => { '!=' => \@users })->search;
$view->filterb( user => { '!=' => \@users })->search;
=head2 Docs that contain the C<$user> in any field
$view->queryb ( $user )->search;
$view->filterb( $user )->search;
=head2 Docs where C<status> is C<active> and any field contains C<$user>:
$view->queryb ( '' => $user, status => 'active' )->search
$view->filterb( '' => $user, status => 'active' )->search
12 changes: 6 additions & 6 deletions lib/Elastic/Model/View.pm
Expand Up @@ -7,7 +7,7 @@ use Elastic::Model::Types qw(
IndexNames ArrayRefOfStr SortArgs IndexNames ArrayRefOfStr SortArgs
HighlightArgs Consistency Replication); HighlightArgs Consistency Replication);
use MooseX::Types::Moose qw(Str Int HashRef ArrayRef Bool Num Object); use MooseX::Types::Moose qw(Str Int HashRef ArrayRef Bool Num Object);

use Elastic::Model::SearchBuilder();
use namespace::autoclean; use namespace::autoclean;


#=================================== #===================================
Expand Down Expand Up @@ -215,7 +215,7 @@ has 'search_builder' => (
); );


#=================================== #===================================
sub _build_search_builder { shift->model->es->builder } sub _build_search_builder { Elastic::Model::SearchBuilder->new }
#=================================== #===================================


#=================================== #===================================
Expand Down Expand Up @@ -624,7 +624,7 @@ one or more types.
Specify the query to run in the native Specify the query to run in the native
L<ElasticSearch query DSL|http://www.elasticsearch.org/guide/reference/query-dsl/> L<ElasticSearch query DSL|http://www.elasticsearch.org/guide/reference/query-dsl/>
or use C<queryb()> to specify your query with the more Perlish or use C<queryb()> to specify your query with the more Perlish
L<ElasticSearch::SearchBuilder> query syntax. L<Elastic::Model::SearchBuilder> query syntax.
By default, the query will By default, the query will
L<match all docs|http://www.elasticsearch.org/guide/reference/query-dsl/match-all-query.html>. L<match all docs|http://www.elasticsearch.org/guide/reference/query-dsl/match-all-query.html>.
Expand All @@ -643,7 +643,7 @@ L<match all docs|http://www.elasticsearch.org/guide/reference/query-dsl/match-al
You can specify a filter to apply to the query results using either You can specify a filter to apply to the query results using either
the native ElasticSearch query DSL or, use C<filterb()> to specify your the native ElasticSearch query DSL or, use C<filterb()> to specify your
filter with the more Perlish L<ElasticSearch::SearchBuilder> DSL. filter with the more Perlish L<Elastic::Model::SearchBuilder> DSL.
If a filter is specified, it will be combined with the L</query> If a filter is specified, it will be combined with the L</query>
as a L<filtered query|http://www.elasticsearch.org/guide/reference/query-dsl/filtered-query.html>, as a L<filtered query|http://www.elasticsearch.org/guide/reference/query-dsl/filtered-query.html>,
or (if no query is specified) as a or (if no query is specified) as a
Expand All @@ -669,7 +669,7 @@ results would then be limited to just those docs where C<tag == perl>.
You can specify a post_filter using either the native ElasticSearch query DSL or, You can specify a post_filter using either the native ElasticSearch query DSL or,
use C<post_filterb()> to specify it with the more Perlish use C<post_filterb()> to specify it with the more Perlish
L<ElasticSearch::SearchBuilder> DSL. L<Elastic::Model::SearchBuilder> DSL.
=head2 sort =head2 sort
Expand Down Expand Up @@ -896,7 +896,7 @@ can later be retrieved using L<ElasticSearch/index_stats()>.
$builder = $view->search_builder; $builder = $view->search_builder;
If you would like to use a different search builder than the default If you would like to use a different search builder than the default
L<ElasticSearch::SearchBuilder> for L</"queryb">, L</"filterb"> or L<Elastic::Model::SearchBuilder> for L</"queryb">, L</"filterb"> or
L</post_filterb>, then you can set a value for L</search_builder>. L</post_filterb>, then you can set a value for L</search_builder>.
=head1 DELETE ATTRIBUTES =head1 DELETE ATTRIBUTES
Expand Down
37 changes: 37 additions & 0 deletions t/70_searchbuilder/00-basic.t
@@ -0,0 +1,37 @@
#!perl

use strict;
use warnings;

use Test::More;
use Test::Differences;
use Test::Exception;

BEGIN {
use_ok('Elastic::Model::SearchBuilder') || print "Bail out!";
}

diag "";
diag(
"Testing Elastic::Model::SearchBuilder $Elastic::Model::SearchBuilder::VERSION, Perl $], $^X"
);

my $a;
ok $a = Elastic::Model::SearchBuilder->new, 'Instantiate';
ok $a = $a->new, 'Instantiate from ref';

is( scalar $a->filter(), undef, 'Empty ()' );
is( scalar $a->filter(undef), undef, '(undef)' );
is( scalar $a->filter( [] ), undef, 'Empty []' );
is( scalar $a->filter( {} ), undef, 'Empty {}' );
is( scalar $a->filter( [ [], {} ] ), undef, 'Empty [[]{}]' );
is( scalar $a->filter( { [], {} } ), undef, 'Empty {[]{}}' );
is( scalar $a->filter( { -ids => [] } ), undef, 'IDS=>[]' );
eq_or_diff $a->filter( \{ term => { foo => 1 } } ),
{ filter => { term => { foo => 1 } } },
'\\{}';

throws_ok { $a->filter( 1, 2 ) } qr/Too many params/, '1,2';
throws_ok { $a->filter( [undef] ) } qr/UNDEF in arrayref/, '[undef]';

done_testing;

0 comments on commit 79e9aa3

Please sign in to comment.