at posts/single.html

検索履歴を表示 (Components)

次は RandomNote の特徴である、サイドバーの検索履歴を表示できるようにする。 Rails でどうやって実現するか。 最初は 2日目に覚えた render(:partial) を使おうかと思ったけど、以下の理由で躊躇した。

  • サイドバーの検索履歴から、並び替え順の変更や履歴の削除の処理を行う
  • 一つのコントローラでやると、ページへのアクションと検索履歴へのアクションが混ざって見づらそう

NoteController に、 destroy_search_history や list_search_history なんていうアクションは作りたくないもんね。

調べてみると、 Components という便利な仕組みが使えそうなことが分かった。 render(:partial) がビューだけを共有するのに対して、 Components はコントローラも共有することができる。

  • render(:partial) の場合 … NoteController + _page.rhtml + _search_history.rhtml
  • Components の場合 … (NoteController + _page.rhtml) + (SearchHistoryController + list.rhtml)

ということで、新しいコンポーネント作りに挑戦。

検索履歴のモデル (SearchHistory) 作成

まず、 script/generate を使って検索履歴のモデルを生成する。 script/generate も久しぶりだな。

$ script/generate model SearchHistory
      exists  app/models/
      exists  test/unit/
      exists  test/fixtures/
      create  app/models/search_history.rb
      create  test/unit/search_history_test.rb
      create  test/fixtures/search_histories.yml
      exists  db/migrate
      create  db/migrate/004_create_search_histories.rb

ファイルが4つ作られた。テスト用のファイルを除くと、「search_history.rb」と「004_create_search_histories.rb」の2つ。 まずはマイグレーションファイルを編集し、データベースのスキーマを作る。 検索履歴で必要な情報は、検索語句 (word)、検索結果数 (sum)、検索回数 (count)、最終検索日 (updated_on) の4つ。 それぞれにインデックスを張っておく。

$ vi db/migrate/004_create_search_histories.rb
class CreateSearchHistories < ActiveRecord::Migration
  def self.up
    create_table :search_histories do |t|
      t.column :word, :string
      t.column :sum, :int, :default => 0
      t.column :count, :int, :default => 0
      t.column :updated_on, :time
    end
    add_index :search_histories, :sum
    add_index :search_histories, :count
    add_index :search_histories, :updated_on
  end

  def self.down
    drop_table :search_histories
  end
end

コントローラ (SearchHistoryController) の作成

モデルができたので、次はコントローラを作る。 アクションとビューが少ないので、Page の時とは違って scaffold は使わない。

$ script/generate controller SearchHistory
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/search_history
      exists  test/functional/
      create  app/controllers/search_history_controller.rb
      create  test/functional/search_history_controller_test.rb
      create  app/helpers/search_history_helper.rb

検索履歴に対するアクションは、一覧の表示 (list) と、履歴からの削除 (destory) の2つ。 一覧表示のほうは、とりあえず更新順に並べる (:order => 'updated_on') ことにする。

$ vi app/controllers/search_history_controller.rb
class SearchHistoryController < ApplicationController
  def list
    @search_histries = SearchHistory.find(:all, :limit => 30, :order => 'updated_on')
  end

  def destroy
    SearchHistory.find(params[:id], :order => 'count').destroy
    redirect_to :controller => 'note', :action => 'list'
  end
end

ビューの作成

次にビューを作る。必要なのは一覧表示 (list.rhtml) だけ。

$ vi app/views/search_history/list.rhtml
<ul>
<% for search_history in @search_histories %>
  <li>
  <%= link_to_search(search_history.word) %>
  <%=h search_history.sum %>
  <%= link_to 'del',
      :controller => 'search_history',
      :action => 'destroy',
      :id => search_history %>
  </li>
<% end %>
</ul>

link_to_search は、検索へのリンクを生成するメソッド。 6日目の BracketName で作ったものがそのまま使える。 link_to_search は app/helpers/note_helper.rb で定義しているので、別のコントローラやビューからはそのままでは使えない。 汎用性がありそうなので、共通のヘルパー (app/helpers/application_helper.rb) に移動させておいた。

link_to 'del' は、検索履歴を削除するためのリンク。 :action で SearchHistoryController の destroy アクションを呼ぶのはお決まりの記述だけど、 :controller でコントローラ名を明示的に指定しているのが違う部分。 このビューは NoteController から SearchController を経由して呼ばれるので、コントローラ名を明示的に書いてあげないといけないみたい。

ここまでで下準備は完成。

コンポーネントの呼び出し

いよいよコンポーネントの呼び出し。 レイアウト (app/views/layouts/note.rhtml) から、今作った app/views/search_history/list.rhtml を NoteController 経由で表示するようにする。

<%= render_component :controller => 'search_history', :action => 'list' %>

この一行でおしまい。あっけないくらいに簡単。

検索時に検索履歴に追加

最後は、検索時に検索履歴を追加していく処理を加える。 検索はモデルのメソッド (Page#fulltext_search) でやっているから、ここを編集した方がいいかな。

   def Page.fulltext_search(word, options = {})
     options[:conditions] = ["body like ?", "%#{word}%"]
-    find(:all, options)
+    pages = find(:all, options)
+    if pages.size > 0
+      search_history = SearchHistory.find_by_word(word)
+      unless search_history
+        search_history = SearchHistory.new(:word => word)
+      end
+      search_history.count += 1
+      search_history.sum = pages.size
+      search_history.save
+    end
+    pages
   end

検索結果が 0 件じゃなかったら、検索履歴に加えるようにしている。

コミット

この状態でコミット。リビジョン18になった。

screenshot (Ruby on Rails) - (9)

まだスタイルシートを修正していないので、サイドバーはできていない。

関連する日記