#278 Search with Sunspot
- Download:
- source codeProject Files in Zip (231 KB)
- mp4Full Size H.264 Video (21.6 MB)
- m4vSmaller H.264 Video (11.8 MB)
- webmFull Size VP8 Video (16.1 MB)
- ogvFull Size Theora Video (28.3 MB)
Sunspot est une solution permettant d'ajouter une recherche full-text aux applications Ruby. Elle utilise Solr en arrière-plan et a de nombreuses fonctionnalités sympathiques. Dans cet épisode, nous allons l'utiliser pour ajouter une recherche full-text à une application Rails. Nous utiliserons une application de blog qui nous a déjà servi dans de précédents épisodes.
Cette application contient une page qui affiche un certain nombre d'articles et nous voulons faire en sorte de pouvoir chercher parmi ceux-ci. En utilisant SQL, cela devient rapidement difficile et ce n'est pas, en général, la meilleure approche. Une solution dédiée à la recherche full-text, comme Sunspot, s'avère plus pertinente pour implémenter cette fonctionnalité.
Installer Sunspot
Sunspot est distribuée sous forme de gem et s'installe de la manière habituelle, Gemfile
et bundle
.
source 'http://rubygems.org' gem 'rails', '3.0.9' gem 'sqlite3' gem 'nifty-generators' gem 'sunspot_rails'
Une fois la gem et ses dépendances installées, nous devons générer le fichier de configuration de Sunspot en lançant
$ rails g sunspot_rails:install
Cette commande crée un fichier YML dans /config/sunspot.yml
. Nous n'avons besoin de faire aucun changement aux réglages par défaut.
Sunspot encapsule Solr dans la gem, nul besoin, donc, de l'installer séparément. Cela signifie que tout fonctionne tout de suite, ce qui est bien pratique en développement. Pour démarrer, nous lançons
$ rake sunspot:solr:start
Si vous êtes sous OS X Lion et que vous n'avez pas installé le runtime Java, il vous sera demandé de le faire lorsque vous lancerez la commande. Vous verrez peut-être un warning de dépréciation mais il peut être ignoré. La commande va également créer quelques fichiers de configuration supplémentaire pour un réglage plus avancé. Nous ne les verrons pas mais ils sont détaillés dans la documentation.
Utiliser Sunspot
Maintenant que nous avons installé Sunspot, nous pouvons l'utiliser dans notre modèle Article
. Pour ajouter la recherche full-text, nous utilisons la méthode searchable
.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :content end end
Cette méthode prend un bloc et, dedans, nous définissons les attributs sur lesquels nous voulons effectuer la recherche de manière à ce que Sunspot sache quoi indexer. Nous pouvons utiliser la méthode text
pour définir les attributs sur lesquels une recherche full-text sera effectuée. Pour nos articles, nous le ferons pour les champs name et content.
Sunspot indexe automatiquement toute nouvel article mais pas les existants. Nous pouvons dire à Sunspot de re-indexer les entrées existantes en lançant
$ rake sunspot:reindex
Tous les articles sont maintenant dans la base de données de Solr et nous pouvons chercher parmi ceux-ci. Nous allons donc ajouter un champ de recherche en haut de la page d'index.
<% title "Articles" %> <%= form_tag articles_path, :method => :get do %> <p> <%= text_field_tag :search, params[:search] %> <%= submit_tag "Search", :name => nil %> <% end %> <!-- rest of view omitted -->
Ce formulaire envoie les information de recherche en GET à l'action index
. Tous les paramètres de recherche seront donc ajoutés à la query string. Nous allons ensuite modifier le contrôleur de façon à ce qu'il récupère les articles en utilisant ce paramètre search
. Pour lancer une recherche avec Sunspot, nous appelons la méthode search
sur le modèle et lui passons un bloc. Dans ce bloc, nous pouvons faire appel à différentes méthodes pour gérer les recherches complexes. Nous allons utiliser la méthode fulltext
et lui passer les paramètres de recherche du formulaire. Enfin, nous allons assigner le résultat à @search
. Nous pouvons appeler results
dessus pour obtenir la liste des articles correspondants.
def index @search = Article.search do fulltext params[:search] end @articles = @search.results end
Nous pouvons tester cela en rechargeant la page des articles et en cherchant un mot clé. Nous voyons alors une liste des articles répondant à cette recherche.
La recherche retourne une liste des articles contenant le terme de recherche, que cela soit dans le champ name
ou content
.
Nous pouvons faire bien plus dans le bloc searchable
du modèle Article
. Par exemple, nous pouvons utiliser boost
pour modifier le poids des champs. Nous pouvons donc rendre le champ name
plus important que le champ content
.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :boost => 5 text :content end end
C'est important lorsque nous voulons trier les résultats par pertinence. Dans notre cas, les articles dont le nom contient le terme recherché apparaîtront avant ceux le comprenant seulement dans leur contenu.
Les attributs listés dans le bloc searchable
ne doivent pas nécessairement être des champs en base de données, nous pouvons utiliser n'importe quelle méthode définie dans le modèle. Nous allons créer une méthode publish_month
qui va retourner le nom du mois et l'année de publication de l'article et effectuer ensuite une recherche sur cette méthode comme s'il s'agissait d'un champ en base.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :boost => 5 text :content, :publish_month end def publish_month published_at.strftime("%B %Y") end end
Nous devons indexer de nouveau les entrées en lançant rake sunspot:reindex
avant de pouvoir effectuer notre recherche sur le nom de mois.
Au lieu de créer une méthode, nous pouvons créer un bloc et effectuer la recherche sur ce que retourne ce dernier. Un article a des commentaires, nous pouvons donc ajouter la possibilité d'effectuer une recherche sur le contenu des commentaires en utilisant un bloc.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :boost => 5 text :content, :publish_month text :comments do comments.map(&:content) end end def publish_month published_at.strftime("%B %Y") end end
Dans le bloc, le contexte est celui d'un Article
. Nous pouvons donc obtenir les commentaires et faire un mapping sur leur contenu. Bien que cela retourne un tableau, Sunspot saura s'en charger et indexer tous les commentaires afin de pouvoir effectuer une recherche sur ces derniers.
Effectuer une recherche sur les attributs
Que devons-nous faire si nous souhaitons des capacités de recherche plus puissantes qu'une simple recherche full-text ? Une recherche sur un attribut particulier, par exemple. Nous devons simplement passer le type d'attribut que nous voulons filtrer, que ce soit string, integer, float ou encore timestamp. Pour ajouter l'attribut published_at
aux champs recherchés, nous pouvons utiliser la méthode time
.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :boost => 5 text :content, :publish_month text :comments do comments.map(&:content) end time :published_at end def publish_month published_at.strftime("%B %Y") end end
Nous pouvons en faire usage dans ArticlesController
pour restreindre les articles cherchés à une date de publication antérieure à la date courante. Pour cela, nous utilisons la méthode with
.
def index @search = Article.search do fulltext params[:search] with(:published_at).less_than(Time.zone.now) end @articles = @search.results end
Cela fait, une recherche ne retournera pas les articles qui n'ont pas encore été publiés. Vous pouvez trouver une très bonne documentation sur le sujet sur la page de wiki de Sunspot.
Recherche à facettes
Une recherche à facettes nous permet de filtrer les résultats de recherche en se basant sur certains attributs comme le mois de publication d'article. Disons que nous voulons ajouter une liste de liens montrant les dates auxquelles des articles ont été publiés. Lorsque nous cliquons sur l'un de ces liens, la liste des articles est alors filtrée pour afficher uniquement ceux publiés pour le mois choisi.
Pour ce faire, nous allons d'abord ajouter un attribut string
au bloc searchable
pour notre méthode publish_month
.
class Article < ActiveRecord::Base attr_accessible :name, :content, :published_at has_many :comments searchable do text :name, :boost => 5 text :content, :publish_month text :comments do comments.map(&:content) end time :published_at string :publish_month end def publish_month published_at.strftime("%B %Y") end end
Nous pouvons en faire une recherche à facettes en appelant facet
dans le bloc search
du contrôleur ArticlesController
.
def index @search = Article.search do fulltext params[:search] with(:published_at).less_than(Time.zone.now) facet(:publish_month) end @articles = @search.results end
Nous pouvons maintenant lister ces facettes sur la page index
en ajoutant le code suivant entre le champ de recherche et la liste des articles.
<div id="facets"> <h3>Published</h3> <ul> <% for row in @search.facet(:publish_month).rows %> <li> <% if params[:month].blank? %> <%= link_to row.value, :month => row.value %> (<%= row.count %>) <% else %> <strong><%= row.value %></strong> (<%= link_to "remove", :month => nil %>) <% end %> </li> <% end %> </ul> </div>
Dans ce code, nous bouclons sur chacune des facettes publish_month
et les affichons. Si nous appelons .facet
sur notre objet @search
et passons l'attribut que nous voulons lister, dans notre cas :publish_month
, nous pouvons ensuite appeler .rows
dessus pour obtenir chaque option de facette pour cet attribut.
Lorsque nous appelons row.value
, la valeur de l'attribut est retournée (ex: “January 2011”). Nous pouvons également faire appel à row.count
pour obtenir le nombre d'articles correspondant à cette valeur. S'il y a un paramètre month
dans la query string, nous allons afficher sa valeur suivie d'un lien “remove” qui permet de supprimer le paramètre. Cela nous permet de sélectionner une facette donnée et de la passer sous forme d'un paramètre month
.
Lorsque nous rechargeons la page, et que nous avons reindexé les entrées, nous allons voir la liste des facettes dans un panneau, chacune montrant un mois et le nombre d'articles publiés durant ce mois. Si nous sélectionnons un mois, nous allons le recevoir en paramètre dans la query string mais les articles ne seront pas filtrés. Pour corriger cela, nous devons ajouter un autre paramètre with
au bloc search
dans le contrôleur afin qu'il filtre les articles d'après le paramètre month
s'il est présent.
def index @search = Article.search do fulltext params[:search] with(:published_at).less_than(Time.zone.now) facet(:publish_month) with(:publish_month, params[:month]) ↵ if params[:month].present? end @articles = @search.results end
Maintenant, lorsque nous sélectionnons un mois, nous verrons la liste correctement filtrée.
Lorsque nous cliquons sur le lien “remove”, nous revenons à la liste complète. Cela fonctionne avec les résultats de recherches. Si nous cherchons un terme, la liste affichera les mois contenant des articles correspondants.
Les facettes sont une fonctionnalité fort sympathique en parallèle de la recherche.
C'est tout pour cet épisode. Sunspot est un très bon moyen d'ajouter une recherche full-text aux applications Rails et fournit de nombreuses fonctionnalités supplémentaires non abordées ici. Pensez à jeter un oeil sur le wiki pour plus d'informations.