Vamos a añadir a nuestra página capacidades de buscas contenido en los tweets de los usuarios. Podríamos hacerlo mediante una consulta en la base de datos. Esto funcionaría bien con pocos tweets. Pero cuando el número de tweets creciera no tendríamos un buen rendimiento, tenemos que indexar el contenido de los tweets para poder hacer búsquedas mucho mas eficientes.

Para añadir esta funcionalidad vamos a utilizar el módulo Elastic Search creado por @_felipera.

Lee la documentación del módulo pasar saber cómo instalarlo

http://www.playframework.org/modules/elasticsearch-0.2/home

Cuanto instalé el módulo no me descargó correctamente las dependencias. Tuve que modificar mi fichero de dependencias y añadirlas a mano.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Application dependencies

require:
    - play
    - play -> secure
    - net.sf.flexjson -> flexjson 2.1
    - play -> elasticsearch 0.1
    - org.elasticsearch -> elasticsearch 0.16.2
    - se.scalablesolutions.akka -> akka-amqp 1.1.2
    
    
repositories:
    - elasticsearch:
        type: iBiblio
        root: "http://oss.sonatype.org/content/repositories/releases/"
        contains:
            - org.elasticsearch -> *
            
    - akka:
        type: iBiblio
        root: "http://akka.io/repository/"
        contains:
            - se.scalablesolutions.akka -> *

Cuando tengamos el módulo instalado. Lo único que debemos hacer para habilitar las búsquedas es anotar nuestra clase.

apps/models/Tweet.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@ElasticSearchable
@Entity
public class Tweet extends Model {

<span class="nd">@Required</span>
<span class="nd">@MaxLength</span><span class="o">(</span><span class="mi">140</span><span class="o">)</span>
<span class="kd">public</span> <span class="n">String</span> <span class="n">msg</span><span class="o">;</span>

<span class="nd">@ElasticSearchEmbedded</span><span class="o">(</span><span class="n">fields</span><span class="o">={</span><span class="s">"username"</span><span class="o">})</span>
<span class="nd">@ManyToOne</span>
<span class="kd">public</span> <span class="n">User</span> <span class="n">author</span><span class="o">;</span>

<span class="kd">public</span> <span class="n">Date</span> <span class="n">date</span><span class="o">;</span>

<span class="kd">public</span> <span class="kd">static</span> <span class="n">Tweet</span> <span class="nf">create</span><span class="o">(</span><span class="n">String</span> <span class="n">msg</span><span class="o">,</span> <span class="n">User</span> <span class="n">author</span><span class="o">){</span>
    <span class="n">Tweet</span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Tweet</span><span class="o">();</span>
    <span class="n">t</span><span class="o">.</span><span class="na">msg</span> <span class="o">=</span> <span class="n">msg</span><span class="o">;</span>
    <span class="n">t</span><span class="o">.</span><span class="na">author</span> <span class="o">=</span> <span class="n">author</span><span class="o">;</span>
    <span class="n">t</span><span class="o">.</span><span class="na">date</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Date</span><span class="o">();</span>
    <span class="k">return</span> <span class="n">t</span><span class="o">;</span>
<span class="o">}</span>    

}

Con el constructor que teníamos creado anteriormente, la búsqueda daba problemas. Por eso es mejor transformar el constructor en un método estático create que realice la misma acción. Recuerda cambiar todas las referencias al constructor que teníamos.

Creamos un método en el controlador que se encargue de hacer la búsqueda.

app/controllers/Timeline.java

1
2
3
4
5
6
7
public static void search(String query){
    if(query != null && !query.isEmpty()){
        SearchResults<Tweet> tweets = ElasticSearch.search(QueryBuilders.fieldQuery(“msg”, query), Tweet.class);
        render(tweets, query);
    }
    render();
}

Creamos la vista

app/views/Timeline/search.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#{form @Timeline.search()}
    <input type=“text” name=“query” value=“${query}” class=“xxlarge”/>
    <input type=“submit” class=“btn primary” value=“Buscar” />

{/form}

{if tweets}

<h3>Encontrados ${tweets.totalCount} tweets para la consulta ‘${query}’</h3> <table> <thead> <tr> <th>id</th> <th>Tweet</th> </tr> </thead> <tbody> #{list items:tweets.objects, as:‘tweet’} <tr> <td>${tweet.id}</td> <td>${tweet.msg}</td>
</tr>
#{/list} </tbody> </table>

{/if}

Ejercicio

Implementar la búsqueda en tiempo real.

Solución

app/controllers/Api.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void search(String query){
    ApiResponse resp = new ApiResponse();   
if(query != null && !query.isEmpty()){ resp.status = “OK”; SearchResults<Tweet> tweets = ElasticSearch.search(QueryBuilders.prefixQuery(“msg”, query), Tweet.class); resp.result = tweets.objects; }else{ resp.status = “ERROR”; resp.message = “Empty query”; }

<span class="n">renderJSON</span><span class="o">(</span><span class="k">new</span> <span class="n">JSONSerializer</span><span class="o">().</span><span class="na">include</span><span class="o">(</span><span class="s">"status"</span><span class="o">,</span> <span class="s">"message"</span><span class="o">,</span> <span class="s">"result.msg"</span><span class="o">,</span> <span class="s">"result.id"</span><span class="o">).</span><span class="na">exclude</span><span class="o">(</span><span class="s">"*"</span><span class="o">).</span><span class="na">serialize</span><span class="o">(</span><span class="n">resp</span><span class="o">));</span>

}

app/views/search.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#{form @Timeline.search()}
    <input type=“text” name=“query” value=“${query}” class=“xxlarge” placeholder=“Busqueda” data-bind=“value:query, valueUpdate:‘afterkeydown’”/>

{/form}

<table> <thead> <tr> <th>id</th> <th>Tweet</th> </tr> </thead> <tbody data-bind=“template:{name : ‘tweet-row’, foreach:tweets}”>

<span class="nt">&lt;/tbody&gt;</span>

</table>

<script type=“text/html” id=“tweet-row”> <tr> <td></td> <td></td>
</tr> </script>

{set ‘endScript’}

<script> viewModel = { query : ko.observable(‘${query}’), tweets : ko.observableArray([]) }

<span class="kd">function</span> <span class="nx">Tweet</span><span class="p">(</span><span class="nx">id</span><span class="p">,</span> <span class="nx">msg</span><span class="p">){</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">id</span><span class="p">;</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">msg</span> <span class="o">=</span> <span class="nx">msg</span><span class="p">;</span>
<span class="p">}</span>

<span class="nx">ko</span><span class="p">.</span><span class="nx">dependentObservable</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">lastQueryRequest</span><span class="p">)</span> <span class="k">this</span><span class="p">.</span><span class="nx">lastQueryRequest</span><span class="p">.</span><span class="nx">abort</span><span class="p">();</span>
    <span class="kd">var</span> <span class="nx">action</span> <span class="o">=</span> <span class="err">#</span><span class="p">{</span><span class="nx">jsAction</span> <span class="err">@</span><span class="nx">Api</span><span class="p">.</span><span class="nx">search</span><span class="p">(</span><span class="s1">':query'</span><span class="p">)</span><span class="o">/</span><span class="p">}</span>
    <span class="k">this</span><span class="p">.</span><span class="nx">lastQueryRequest</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="nx">action</span><span class="p">({</span><span class="na">query</span> <span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">query</span><span class="p">()}),</span> <span class="kd">function</span><span class="p">(</span><span class="nx">data</span><span class="p">){</span>
        <span class="k">if</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">status</span> <span class="o">===</span> <span class="s1">'OK'</span><span class="p">){</span>
            <span class="kd">var</span> <span class="nx">tweets</span> <span class="o">=</span> <span class="nx">$</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">data</span><span class="p">.</span><span class="nx">result</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">item</span><span class="p">){</span><span class="k">return</span> <span class="k">new</span> <span class="nx">Tweet</span><span class="p">(</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span> <span class="nx">item</span><span class="p">.</span><span class="nx">msg</span><span class="p">);});</span>
            <span class="nx">viewModel</span><span class="p">.</span><span class="nx">tweets</span><span class="p">(</span><span class="nx">tweets</span><span class="p">);</span>      
        <span class="p">}</span>
    <span class="p">});</span>
<span class="p">},</span> <span class="nx">viewModel</span><span class="p">);</span>

<span class="nx">ko</span><span class="p">.</span><span class="nx">applyBindings</span><span class="p">(</span><span class="nx">viewModel</span><span class="p">);</span>

</script>

{/set}