File Coverage

blib/lib/MojoMojo/Model/Search.pm
Criterion Covered Total %
statement 82 83 98.8
branch 11 20 55.0
condition 3 5 60.0
subroutine 15 15 100.0
pod 6 6 100.0
total 117 129 90.7


line stmt bran cond sub pod time code
1             package MojoMojo::Model::Search;
2              
3 35     35   19443 use strict;
  35         89  
  35         1253  
4              
5 35     35   214 use parent 'Catalyst::Model';
  35         89  
  35         232  
6              
7 35     35   16726 use KinoSearch1::InvIndexer;
  35         2626003  
  35         424  
8 35     35   14182 use KinoSearch1::Searcher;
  35         885637  
  35         421  
9 35     35   14978 use KinoSearch1::Analysis::PolyAnalyzer;
  35         133980  
  35         380  
10 35     35   948 use KinoSearch1::Index::Term;
  35         90  
  35         218  
11 35     35   656 use KinoSearch1::Search::Query;
  35         88  
  35         298  
12 35     35   725 use KinoSearch1::QueryParser::QueryParser;
  35         80  
  35         191  
13              
14             __PACKAGE__->config->{index_dir} ||= MojoMojo->config->{index_dir};
15             # Fall back just in case MojoMojo->config->{index_dir} doesn't exist
16             # but it should. See MojoMojo.pm to verify that we will short circuit
17             # on this next line.
18             __PACKAGE__->config->{index_dir} ||= MojoMojo->path_to('/index');
19              
20             =head1 NAME
21              
22             MojoMojo::Model::Search - support for searching pages
23              
24             =head1 METHODS
25              
26             =cut
27              
28             my $invindexer;
29             my $analyzer = KinoSearch1::Analysis::PolyAnalyzer->new( language => _get_language() );
30              
31             =head2 indexer
32              
33             Index the search data
34              
35             =cut
36              
37             sub indexer {
38 9     9 1 27 my $self = shift;
39             my $invindexer = KinoSearch1::InvIndexer->new(
40             invindex => __PACKAGE__->config->{index_dir},
41             create =>
42 9 100       96 ( -f __PACKAGE__->config->{index_dir} . '/segments' ? 0 : 1 ),
43             analyzer => $analyzer,
44             );
45 9         35879 $invindexer->spec_field( name => 'path', analyzed => 0 );
46 9         2651 $invindexer->spec_field( name => 'text' );
47 9         2043 $invindexer->spec_field( name => 'author' );
48 9         2178 $invindexer->spec_field( name => 'date', analyzed => 0 );
49 9         2135 $invindexer->spec_field( name => 'tags' );
50 9         1977 return $invindexer;
51             }
52              
53             =head2 searcher
54              
55             Used by search() to do the grunt work.
56              
57             =cut
58              
59             sub searcher {
60 3     3 1 9 my $self = shift;
61             $self->prepare_search_index
62 3 50       26 unless -f __PACKAGE__->config->{index_dir} . '/segments';
63             return KinoSearch1::Searcher->new(
64             invindex => __PACKAGE__->config->{index_dir},
65 3         420 analyzer => $analyzer,
66             );
67             }
68              
69             =head2 prepare_search_index
70              
71             Create a new search index from all pages in the database.
72             Will do nothing if the index already exists.
73              
74             =cut
75              
76             sub prepare_search_index {
77 1     1 1 378 my $self = shift;
78              
79 1 50       9 MojoMojo->log->info("Initializing search index...")
80             if MojoMojo->debug;
81              
82             # loop through all latest-version pages
83 1         11 my $count = 0;
84 1         8 my $it = MojoMojo->model('DBIC::Page')->search;
85 1         1411 while ( my $page = $it->next ) {
86 3         4514 $page->result_source->resultset->set_paths($page);
87 3         67 $self->index_page($page);
88 3         14822 $count++;
89             }
90              
91 1 50       208 MojoMojo->log->info("Indexed $count pages") if MojoMojo->debug;
92             }
93              
94             =head2 index_page <page>
95              
96             Create/update the search index with data from a MojoMojo page when it changes.
97              
98             =cut
99              
100             sub index_page {
101 8     8 1 1778 my ( $self, $page ) = @_;
102 8         43 my $index = $self->indexer;
103 8         243 $page->discard_changes();
104 8 50 33     56392 return unless ( $page && $page->content );
105              
106 8         45088 my $content = $page->content;
107 8         129 my $key = $page->path;
108              
109 8         354 my $text = $content->body;
110 8 50       336 $text .= " " . $content->abstract if ( $content->abstract );
111 8 50       277 $text .= " " . $content->comments if ( $content->comments );
112              
113             # translate the path into plain text so we can use it in the search query later
114 8         125 my $fixed_path = $key;
115 8         40 $fixed_path =~ s{/}{X}g;
116              
117 8         102 my $term = KinoSearch1::Index::Term->new( path => $fixed_path );
118 8         131 $index->delete_docs_by_term($term);
119 8         14159 my $doc = $index->new_doc();
120 8         2577 $doc->set_value( author => $content->creator->login );
121 8         82955 $doc->set_value( path => $fixed_path );
122 8 50       275 $doc->set_value(
123             date => ( $content->created ) ? $content->created->ymd : '' );
124 8         5824 $doc->set_value( tags => join( ' ', map { $_->tag } $page->tags ) );
  0         0  
125 8         34373 $doc->set_value( text => $text );
126 8         124 $index->add_doc($doc);
127 8         12743 $index->finish( optimize => 1 );
128             }
129              
130             =head2 search
131              
132             Search for a term or phrase.
133              
134             =cut
135              
136             sub search {
137 3     3 1 203 my ( $self, $q ) = @_;
138 3         54 my $qp = KinoSearch1::QueryParser::QueryParser->new(
139             analyzer => $analyzer,
140             fields => [ 'text', 'tags' ],
141             default_boolop => 'AND'
142             );
143 3         505 my $query = $qp->parse($q);
144 3         1102 my $hits = $self->searcher->search( query => $query );
145              
146 3         8287 return $hits;
147             }
148              
149             =head2 delete_page <page>
150              
151             Removes a page from the search index.
152              
153             =cut
154              
155             sub delete_page {
156 1     1 1 125 my ( $self, $page ) = @_;
157              
158 1 50       9 return unless $page;
159              
160 1         9 my $index = $self->indexer;
161 1         10 my $path = $page->path;
162 1         12 $path =~ s{/}{X}g;
163              
164 1         15 my $term = KinoSearch1::Index::Term->new( path => $path );
165 1         23 $index->delete_docs_by_term($term);
166 1         2830 $index->finish( optimize => 1 );
167             }
168              
169             sub _get_language {
170 35     35   96 my %supported_lang = map { $_ => 1 } qw( en da de es fi fr it nl no pt ru sv );
  420         911  
171 35   100     235 my $default_lang = __PACKAGE__->config->{default_lang} || MojoMojo->config->{default_lang} || 'en';
172              
173 35 50       5979 return exists $supported_lang{$default_lang} ? $default_lang : 'en';
174             }
175              
176             =head1 AUTHOR
177              
178             Marcus Ramberg <mramberg@cpan.org>
179              
180             =head1 LICENSE
181              
182             This library is free software. You can redistribute it and/or modify
183             it under the same terms as Perl itself.
184              
185             =cut
186              
187             1;