検索履歴を表示 (Components)
2006-10-13
次は 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になった。
まだスタイルシートを修正していないので、サイドバーはできていない。