2016年10月にオーム社から「新装版 達人プログラマー 職人から名匠への道」というプログラマーとしてよい仕事をするための本が出版されました。
新装版 達人プログラマー 職人から名匠への道
オーム社
¥ 3,456
本書は1999年に出版された英語の本の翻訳です。そのため、内容は1999年当時のものです。そう聞くと時代遅れの内容だと感じます。しかし、驚くべきことに2016年のいま読んでも時代遅れになっている内容がほとんどありません。これは、本書の内容が一過性のものではなく時代に左右されにくい本質的なことだったということです。
そのため、これからプログラマーとしてよい仕事をしたい若い人にオススメしたい本です。(一過性の内容ではなく)本質的な内容を学べることはすばやくレベルアップすることにつながるからです。
ただ、若い人はまだピンとこない内容かもしれません。ある程度経験がないとピンとこないこともあるためです。ピンとくるかどうか不安な人は、本屋で立ち読みでもよいので「第1章 達人の哲学」を読んでみてください。30ページくらいです。ここだけでピンとくるか判断できるくらいの量の本質的な内容があります。不安な人はまずは第1章を確認するとよいでしょう。オーム社の新装版 達人プログラマーのページでサンプルファイルをダウンロードできるのですが、そのファイルでは第1章の途中まで確認できます。こちらも活用してください。
参考までに第1章の最初の段落を引用します。
常人と達人プログラマーとの違いは何でしょうか? それは、問題に対するアプローチとその解決手段についての考え方、スタイル、哲学であると言っていいでしょう。達人プログラマーは、眼前の問題を考えるだけでなく、常にその問題をより大きな背景の中で捉え、常により大きな問題を見つけ出そうとするのです。要するに、大きな背景を捉えることなしに、達人たり得る方法はありませんし、知的な解決、見識のある決定を行う方法もあり得ないのです。
若い人はこれからどんなプログラマーとしてやっていきたいか考えてみてください。与えられた作業を指示された方法で実現するプログラマーとしてやっていきたいでしょうか。なぜその作業をする必要があるのか、問題を解決する方法は提示された方法で妥当なのか、そもそもその問題設定は妥当なのか、そんなことを考え、よりよい決定をしながら問題を解決していく、そんなプログラマーとしてやっていきたいでしょうか。
2000年にもこの本の翻訳書が出版されていました。当時からよい内容という評価だったため、10年以上前にその本を読んだことがある人もいるでしょう。そんな人はぜひもう一度本書を読んでみてください。10年以上前にこうあるべきだよなぁと思ったことをいま実現できているかを確認する機会になるはずです。もしかしたら、当時はピンときていなかった内容が今はとてもグッとくる内容になっているかもしれません。
ぜひ再び本書を読んでみてください。
よい仕事をしたいプログラマー向けの本、「新装版 達人プログラマー 職人から名匠への道」を紹介しました。よい仕事をしたいプログラマーは若い人でも若くない人でもぜひ読んでみてください。本質的な内容が書いてあります。
以前、OSDNのファイルリリース機能をAPI経由で使うにはという記事で機能を紹介したきりになっていましたが、milter managerというプロジェクトで全面的にSourceForge.netからOSDNに移行したので備忘録を兼ねて記事にします。
移行の理由は以下の通りです。
milter managerは最初のバージョンをリリースした2009年から7年間SourceForge.netにお世話になってきました。SourceForge.netがなかったらmilter managerの開発にはもっと手間と費用がかかっていたはずです。SourceForge.netには大変感謝しています。
前提として、ソースコードリポジトリは既にGitHubに移行済みとします*1。
機能 | 移行可否 | 備考 |
---|---|---|
メーリングリスト | 英語のメーリングリストを作るときは、管理画面から言語設定を変更する。データの移行はできない。 | |
フォーラム | データの移行はできない。ほとんど使用されていなかった | |
Webサイトホスティング | rsyncが使えるのでそのままのディレクトリ構造で丸ごと移行できる。SourceForge.net側に.htaccessでリダイレクト設定ができる。 | |
ファイルリリース | 自動化もできた | |
ニュース | なし | |
apt/yumリポジトリ | OSDNにはapt/yumをホストできる機能がないのでpackagecloudに移行することにした |
機能的にはほとんどの部分で代替できることがわかったので、移行することにしました。
メーリングリストは以下の4つを作成していました。
milter-manager-bugsはほとんど使用されていなかったので移行しないことに決めました。それ以外の3つはOSDNでメーリングリストを作成しました。このときmilter-manager-users-enは英語のわかる人向けなので入会用ページの表示言語を英語にしました。なお、メーリングリストに登録されているメンバーは数が少なかったので手動で移行しました。
Webサイトのホスティングは問題なくできました。rsyncを使ってSourceForge.netのときと同じディレクトリ構成でアップロードすることができました。 SourceForge.net側で.htaccessにリダイレクトの設定をすることができるので、301リダイレクトの設定をしておきました。
Redirect permanent / https://milter-manager.osdn.jp/
ファイルリリースの自動化については以前の記事を参照してください。
SourceForge.netのときもニュース機能を使用していましたが、この記事を書くまで存在を忘れていました*2。
OSDNにはapt/yumリポジトリを置けない問題があるのでpackagecloudにapt/yumリポジトリについてはpackagecloudに移行することにしました。
packagecloudではRubyGems,deb,RPMなどいくつかのパッケージを配布することができます。価格を見るとオープンソースのプロジェクトだと25GBの容量を無料で使えることがわかりました。milter managerで使用するために申し込んだところ、ほぼ即日使えるようになりました。無料で使わせてもらうかわりに、Assets and Brand Guidelinesにある画像を使ってリンクを張る必要がありました。
パッケージのアップロードもコマンド一発で簡単にできました。
一つだけ問題があってDebian GNU/Linuxのsid(unstable)向けのパッケージを配布できるようにはなっていませんでした。理由をサポートに問い合わせたところ
sidは日々パッケージ構成が変わるのでpackagecloud側で動作環境を保証できないため提供していない
とのことでした*3。この件については、milter managerをなるべく早くDebianのオフィシャルパッケージにすることで対応しようと考えています。なお、サポートの返信はとても早く対応もとても丁寧でした。
Launchpadなどと違ってローカルでパッケージをビルドする必要がありますが、ビルドしたパッケージの動作確認をしてからアップロードできるという利点があります。パッケージのビルド部分はそのままで、アップロード部分のみの変更で済んだのもmilter managerプロジェクトとしては利点でした。Launchpadのようにビルドサーバーも提供しているようなサービスの場合、必要なファイルをアップロードしてからパッケージをビルドするため、ビルドに失敗したり、ビルドしたパッケージに問題があった場合に再アップロードが必要となるため修正するのに時間がかかるという欠点があります。
他の作業と並行して進めていたので、2ヶ月弱くらいかかりましたが特にハマることなく移行できました。 メーリングリストや rsync によるウェブサイトホスティング等、GitHub や Bitbucket 等にはない便利な機能があるのでSourceForge.netからの移行先としてOSDNを検討してみるのはいかがでしょうか。
2016年12月1日から12月3日にかけて開催されたPostgreSQLの国際カンファレンスPGConf.ASIA 2016でPGroongaというPostgreSQLの全文検索モジュールの話をしました。
関連リンク:
前半はPostgreSQLの全文検索まわりの課題の説明とPGroongaがどうやってそれらの課題を解決するかという内容になっています。
後半はPGroongaの説明になっています。
PostgreSQLの全文検索まわりの課題は次の通りです。
PGroongaはこれらの課題を解決します。PGroongaは全言語対応でヒット数が増えても高速な全文検索機能を提供します。
以下はpg_bigmとの検索時間の比較です。棒が短いほど速いです。pg_bigm(青い棒)は極端に遅くなるケースがありますが、PGroonga(紫の棒)は安定して高速です。
この検索時間の比較はデータとして日本語版Wikipediaを用いています。他にもデータとして英語版Wikipediaを用いて、PGroongaとPostgreSQLが標準で提供する全文検索機能を比較したデータもあります。詳細はスライドの内容あるいは以下のドキュメントを参照してください。
後半のPGroongaの説明では以下のことに触れました。詳細はスライドと以下のリストに含まれているリンクを参考にしてください。
PGConf.ASIA 2016でPGroongaについて話しました。現在のPostgreSQLの全文検索機能には課題がありますが、PGroongaを組み込むことで、PostgreSQLでアジア圏の言語(もちろん日本語も含む!)でも実用的な全文検索機能を実現することができます。PostgreSQLでの全文検索機能でこまったらPGroongaを検討してみてください。
MySQL・PostgreSQL・SQLite3の標準機能では日本語テキストの全文検索に難があります。MySQL・PostgreSQLに高速・高機能な日本語全文検索機能を追加するMroonga・PGroongaというプラグインがあります。これらを導入することによりSQLで高速・高機能な日本語全文検索機能を実現できます。詳細は以下を参照してください。
ここではMroonga・PGroongaを使わずに日本語全文検索を実現する方法を紹介します。それはGroongaを使う方法です。
GroongaはMroonga・PGroongaのバックエンドで使われている全文検索エンジンです。
Groongaを直接使うメリットは以下の通りです。
一方、デメリットは以下の通りです。
SELECT
のWHERE
での条件の書き方を学習するくらいでよいが、Groongaはインデックスの設計やクエリーの書き方について学習する必要がある)このデメリットのうち学習コストの方をできるだけ抑えつつGroongaを使えるようにするためのライブラリーがあります。それがgroonga-client-railsです。groonga-client-railsがGroongaを使う部分の多くをフォローしてくれるため利用者は学習コストを抑えたままGroongaを使って高速な日本語全文検索システムを実現できます。
この記事ではRuby on Railsで作ったアプリケーションからGroongaを使って日本語全文検索機能を実現する方法を説明します。実際にドキュメント検索システムを開発する手順を示すことで説明します。ここではCentOS 7を用いますが、他の環境でも同様の手順で実現できます。
なお、この記事ではMySQL・PostgreSQLではなくSQLite3を使っていますが、アプリケーションのコードは変更せずにMySQL・PostgreSQLでも動くので気にしないでください。
@KitaitiMakotoさんが書いたgroonga-client-railsの使い方を紹介した記事もあるのでそちらもあわせて参照してください。違った視点で紹介しているので理解が深まるはずです。
まずGroongaをインストールします。CentOS 7以外の場合にどうすればよいかはGroongaのインストールドキュメントを参照してください。
% sudo -H yum install -y http://packages.groonga.org/centos/groonga-release-1.2.0-1.noarch.rpm
% sudo -H yum install -y groonga-httpd
% sudo -H systemctl start groonga-httpd
CentOS 7にはRuby 2.0のパッケージがありますが、Ruby on Rails 5.0.1はRuby 2.2以降が必要なのでrbenvとruby-buildでRuby 2.3をインストールします。
% sudo -H yum install -y git
% git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
% git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
% echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
% echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
% exec ${SHELL} --login
% sudo -H yum install -y gcc make patch openssl-devel readline-devel zlib-devel
% rbenv install 2.3.3
% rbenv global 2.3.3
Ruby on Railsをインストールします。
% sudo -H yum install -y sqlite-devel nodejs
% gem install rails
いよいよ日本語全文検索機能を持ったドキュメント検索システムを開発します。
まずはrails new
で雛形を作ります。
% rails new document_search
% cd document_search
データベースを作成します。
% bin/rails db:create
% bin/rails generate scaffold document title:text content:text
% bin/rails db:migrate
ここまでは(Groongaのインストール以外は)Groongaと関係ない手順です。
ここからはGroongaを使う場合に特有の手順になります。
まずGemfileにgroonga-client-rails gemを追加します。
gem 'groonga-client-rails'
groonga-client-rails gemをインストールします。
% bundle install
それではアプリケーション側に全文検索機能を実装します。
まず、サーチャーというオブジェクトを定義します。これはGroongaでいい感じに全文検索するための機能を提供するオブジェクトです。
サーチャー用のディレクトリーを作成します。
% mkdir -p app/searchers
app/searchers/application_searcher.rb
にApplicationSearcher
を作成します。(ジェネレーターはまだ実装されていません。)
class ApplicationSearcher < Groonga::Client::Searcher
end
Document
モデル用のサーチャーDocumentsSearcher
をapp/searchers/documents_searcher.rb
に作成します。
class DocumentsSearcher < ApplicationSearcher
# Documentモデルのtitleカラムを全文検索するためのインデックスを作成
schema.column :title, {
type: "ShortText",
index: true,
index_type: :full_text_search,
}
# Documentモデルのcontentカラムを全文検索するためのインデックスを作成
schema.column :content, {
type: "Text",
index: true,
index_type: :full_text_search,
}
end
モデルのカラムとサーチャーのインデックスを対応付けるコードをモデルに追加します。
app/models/document.rb
:
class Document < ApplicationRecord
# DocumentモデルをDocumentsSearcherの検索対象とする
source = DocumentsSearcher.source(self)
# Documentのtitleカラムと
# DocumentsSearcherのtitleインデックスを対応付ける
source.title = :title
# Documentのcontentカラムと
# DocumentsSearcherのcontentインデックスを対応付ける
source.content = :content
end
この対応付けをGroongaのサーバーに反映します。
% bin/rails groonga:sync
動作を確認するためにQiitaから検索対象のドキュメントを取得するRakeタスクを作ります。
lib/tasks/data.rake
:
require "open-uri"
require "json"
namespace :data do
namespace :load do
desc "Load data from Qiita"
task :qiita => :environment do
tag = "groonga"
url = "https://qiita.com/api/v2/items?page=1&per_page=100&query=tag:#{tag}"
open(url) do |entries_json|
entries = JSON.parse(entries_json.read)
entries.each do |entry|
Document.create(title: entry["title"],
content: entry["body"])
end
end
end
end
end
実行して検索対象のドキュメントを作成します。
% bin/rails data:load:qiita
http://localhost:3000/documents
にアクセスし、データが入っていることを確認します。
ビューにヒット件数表示機能と検索フォームをつけてコントローラーで全文検索するようにします。
検索フォームではquery
というパラメーターに検索クエリーを指定することにします。
@documents
を@result_set
に変更している理由はあとでわかります。端的に言うとヒットしたドキュメントだけでなくさらに情報も持っているので@result_set
(結果セット)にしています。たとえば、「ヒット数」(@result_set.n_hits
)も持っています。SQLでは別途SELECT COUNT(*)
を実行しないといけませんが、Groongaでは1回の検索で検索結果もヒット数も両方取得できるので効率的です。
なお、ヒットしたドキュメントに対応するDocument
モデルは@result_set.records.each {|record| record.source}
でアクセスできます。そのため、モデルが必要な処理(たとえばURLの生成)もこれまで通りの方法で使えます。
app/views/documents/index.html.erb
:
<h1>Documents</h1>
+<p><%= @result_set.n_hits %> records</p>
+
+<%= form_tag(documents_path, method: "get") do %>
+ <%= search_field_tag "query", @query %>
+ <%= submit_tag "Search" %>
+<% end %>
+
<table>
<thead>
<tr>
@@ -12,7 +19,8 @@
</thead>
<tbody>
- <% @documents.each do |document| %>
+ <% @result_set.records.each do |record| %>
+ <% document = record.source %>
<tr>
<td><%= document.title %></td>
<td><%= document.content %></td>
app/controllers/documents_controller.rb
:
@@ -4,7 +4,11 @@ class DocumentsController < ApplicationController
# GET /documents
# GET /documents.json
def index
- @documents = Document.all
+ @query = params[:query]
+ searcher = DocumentsSearcher.new
+ @result_set = searcher.search.
+ query(@query).
+ result_set
end
# GET /documents/1
この状態で次のようにレコード数とフォームが表示されるようになります。
また、この状態で日本語全文検索機能を実現できています。確認してみましょう。
フォームに「オブジェクト」と日本語のクエリーを入力します。元のドキュメントは100件あり、「オブジェクト」で絞り込んで11件になっています。日本語で全文検索できていますね。
次のようにOR検索もできます。「オブジェクト」単体で検索したときの11件よりも件数が増えているのでORが効いていることがわかります。
これで基本的な全文検索機能は実現できていますが、せっかく全文検索エンジンを直接使って検索しているので全文検索エンジンならではの機能も使ってみましょう。
まずはドリルダウン機能を使います。ドリルダウンとはある軸に注目して情報を絞り込んでいくことです。例えば、商品カテゴリーに注目して商品を絞り込む(例:家電→洗濯機→ドラム式)、タグに注目して記事を絞り込むといった具合です。
まずは各ドキュメントにタグを付けられるようにしましょう。
% bin/rails generate scaffold tags name:string
% bin/rails generate model tagging document:references tag:references
スキーマを更新します。
% bin/rails db:migrate
モデルに関連情報を追加します。
app/models/document.rb
:
@@ -1,4 +1,7 @@
class Document < ApplicationRecord
+ has_many :taggings
+ has_many :tags, through: :taggings
+
source = DocumentsSearcher.source(self)
source.title = :title
source.content = :content
app/models/tag.rb
:
@@ -1,2 +1,4 @@
class Tag < ApplicationRecord
+ has_many :taggings
+ has_many :documents, through: :taggings
end
Qiitaのデータからタグ情報もロードするようにします。
lib/tasks/data.rake
:
@@ -10,8 +10,12 @@ namespace :data do
open(url) do |entries_json|
entries = JSON.parse(entries_json.read)
entries.each do |entry|
+ tags = entry["tags"].collect do |tag|
+ Tag.find_or_create_by(name: tag["name"])
+ end
Document.create(title: entry["title"],
- content: entry["body"])
+ content: entry["body"],
+ tags: tags)
end
end
end
データベース内のデータを削除してQiitaのロードし直します。
% bin/rails runner Document.destroy_all
% bin/rails data:load:qiita
ビューにタグ情報も表示します。
app/views/documents/index.html.erb
:
@@ -14,6 +14,7 @@
<tr>
<th>Title</th>
<th>Content</th>
+ <th>Tags</th>
<th colspan="3"></th>
</tr>
</thead>
@@ -24,6 +25,13 @@
<tr>
<td><%= document.title %></td>
<td><%= document.content %></td>
+ <td>
+ <ul>
+ <% document.tags.each do |tag| %>
+ <li><%= tag.name %></li>
+ <% end %>
+ </ul>
+ </td>
<td><%= link_to 'Show', document %></td>
<td><%= link_to 'Edit', edit_document_path(document) %></td>
<td><%= link_to 'Destroy', document, method: :delete, data: { confirm: 'Are you sure?' } %></td>
「Tags」カラムにタグがあるのでタグがロードされていることを確認できます。
それではこのタグ情報を使ってドリルダウンできるようにします。
Groongaでタグ情報を使えるようにするにはサーチャーとモデルにタグ情報を使うというコードを追加します。
app/searchers/documents_searcher.rb
:
@@ -9,4 +9,11 @@ class DocumentsSearcher < ApplicationSearcher
index: true,
index_type: :full_text_search,
}
+ schema.column :tags, {
+ type: "ShortText",
+ reference: true, # 文字列でドリルダウンをするときは指定すると高速になる
+ normalizer: false, # タグそのもので検索する
+ vector: true, # 値が複数あるときは指定する
+ index: true,
+ }
end
app/models/document.rb
:
@@ -5,4 +5,7 @@ class Document < ApplicationRecord
source = DocumentsSearcher.source(self)
source.title = :title
source.content = :content
+ source.tags = ->(model) do
+ model.tags.collect(&:name) # タグモデルではなくタグ名をGroongaに渡す
+ end
end
マッピングを変更したらgroonga:sync
で同期します。
% bin/rails groonga:sync
これでGroongaでタグ情報を使えるようになりました。フォームに「tags:@全文検索
」と入力すると「全文検索」タグで絞り込めます。(tags:@...
は「tags
カラムの値を検索する」というGroongaの構文です。Googleのsite:...
に似せた構文です。)
ユーザーにとっては、タグをキーボードから入力して絞り込む(ドリルダウンする)のは面倒なので、クリックでドリルダウンできるようにします。
コントローラーには次の2つの処理を追加しています。
tag
が指定されていたらfilter("tags @ %{tag}", tag: tag)
でタグ検索をする条件を追加する。「タグでドリルダウンするための情報を取得する」とはSQLでいうと「GROUP BY tag
の結果も取得する」という処理になります。SQLではGROUP BY
の結果も取得すると追加でSQLを実行しないといけませんが、Groongaでは1回のクエリーで検索もヒット数の取得もドリルダウン用の情報も取得できるので効率的です。
app/controllers/documents_controller.rb
:
@@ -5,9 +5,16 @@ class DocumentsController < ApplicationController
# GET /documents.json
def index
@query = params[:query]
+ @tag = params[:tag]
+
searcher = DocumentsSearcher.new
- @result_set = searcher.search.
- query(@query).
+ request = searcher.search.query(@query)
+ if @tag.present?
+ request = request.filter("tags @ %{tag}", tag: @tag)
+ end
+ @result_set = request.
+ drilldowns("tag").keys("tags").
+ drilldowns("tag").sort_keys("-_nsubrecs").
result_set
end
ビューではクリックでドリルダウンできる(タグで絞り込める)ようにリンクを表示します。
app/views/documents/index.html.erb
:
@@ -5,10 +5,21 @@
<p><%= @result_set.n_hits %> records</p>
<%= form_tag(documents_path, method: "get") do %>
+ <%= hidden_field_tag "tag", @tag %>
<%= search_field_tag "query", @query %>
<%= submit_tag "Search" %>
<% end %>
+<nav>
+ <% @result_set.drilldowns["tag"].records.each do |record| %>
+ <%= link_to_unless @tag == record._key,
+ "#{record._key} (#{record._nsubrecs})",
+ url_for(query: @query, tag: record._key) %>
+ <% end %>
+ <%= link_to "タグ絞り込み解除",
+ url_for(query: @query) %>
+</nav>
+
<table>
<thead>
<tr>
@@ -27,8 +38,10 @@
<td><%= document.content %></td>
<td>
<ul>
- <% document.tags.each do |tag| %>
- <li><%= tag.name %></li>
+ <% record.tags.each do |tag| %>
+ <li><%= link_to_unless @tag == tag,
+ tag,
+ url_for(query: @query, tag: tag) %></li>
<% end %>
</ul>
</td>
これで次のような画面になります。「全文検索 (20)」というリンクがあるので、「全文検索」タグでドリルダウンすると「20件」ヒットすることがわかります。
「全文検索 (20)」のリンクをクリックすると「全文検索」タグでドリルダウンできます。たしかに20件ヒットしています。
ここからさらにキーワードで絞り込むこともできます。以下はさらに「ruby」で絞り込んだ結果です。ヒット数がさらに減って3件になっています。
全文検索エンジンの機能を使うと簡単・高速にドリルダウンできるようになります。
検索結果を確認しているとき、キーワードがどこに含まれているかがパッとわかると目的のドキュメントかどうかを判断しやすくなります。そのための機能も全文検索エンジンならではの機能です。
highlight_html()
を使うとキーワードを<span class="keyword">...</span>
で囲んだ結果を取得できます。
snippet_html()
を使うとキーワード周辺のテキストを取得できます。
これらを使ってキーワードをハイライトするには次のようにします。
app/controllers/documents_controller.rb
:
@@ -13,6 +13,12 @@ class DocumentsController < ApplicationController
request = request.filter("tags @ %{tag}", tag: @tag)
end
@result_set = request.
+ output_columns([
+ "_key",
+ "*",
+ "highlight_html(title)",
+ "snippet_html(content)",
+ ]).
drilldowns("tag").keys("tags").
drilldowns("tag").sort_keys("-_nsubrecs").
result_set
app/views/documents/index.html.erb
:
@@ -34,8 +34,16 @@
<% @result_set.records.each do |record| %>
<% document = record.source %>
<tr>
- <td><%= document.title %></td>
- <td><%= document.content %></td>
+ <td><%= record.highlight_html.html_safe %></td>
+ <td>
+ <% if record.snippet_html.present? %>
+ <% record.snippet_html.each do |chunk| %>
+ <div>...<%= chunk.html_safe %>...</div>
+ <% end %>
+ <% else %>
+ <%= document.content %>
+ <% end %>
+ </td>
<td>
<ul>
<% record.tags.each do |tag| %>
app/assets/stylesheets/documents.scss
:
@@ -1,3 +1,7 @@
// Place all the styles related to the documents controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
+
+.keyword {
+ color: red;
+}
「全文検索」タグでドリルダウンして「ruby」で全文検索した状態では次のようになります。どこにキーワードがあるかすぐにわかりますね。
検索結果の表示順はユーザーが求めていそうな順番にするとユーザーはうれしいです。
Groongaはスコアという数値でどれだけ検索条件にマッチしていそうかという情報を返します。スコアでソートすることでユーザーが求めていそうな順番にできます。
@@ -8,7 +8,12 @@ class DocumentsController < ApplicationController
@tag = params[:tag]
searcher = DocumentsSearcher.new
- request = searcher.search.query(@query)
+ request = searcher.search
+ if @query.present?
+ request = request.
+ query(@query).
+ sort_keys("-_score")
+ end
if @tag.present?
request = request.filter("tags @ %{tag}", tag: @tag)
end
groonga-client-railsは標準でページネーション機能を提供しています。Kaminariと連携することでページネーションのUIもすぐに作れます。
Gemfile
:
@@ -53,3 +53,4 @@ end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
gem 'groonga-client-rails'
+gem 'kaminari'
app/controllers/documents_controller.rb
:
@@ -26,6 +26,7 @@ class DocumentsController < ApplicationController
]).
drilldowns("tag").keys("tags").
drilldowns("tag").sort_keys("-_nsubrecs").
+ paginate(params[:page]).
result_set
end
app/views/documents/index.html.erb
:
@@ -2,7 +2,7 @@
<h1>Documents</h1>
-<p><%= @result_set.n_hits %> records</p>
+<p><%= page_entries_info(@result_set, entry_name: "documents") %></p>
<%= form_tag(documents_path, method: "get") do %>
<%= hidden_field_tag "tag", @tag %>
@@ -63,4 +63,6 @@
<br>
+<%= paginate(@result_set) %>
+
<%= link_to 'New Document', new_document_path %>
RubyGemsを追加したのでGemfile.lock
を更新します。アプリケーションサーバーを再起動することも忘れないでください。
% bundle install
画面の上にはページの情報が表示されます。
画面の下にはページを移動するためのリンクが表示されます。
MySQL・PostgreSQL・SQLite3とGroongaを使ってRuby on Railsアプリケーションで日本語全文検索機能を実現する方法を説明しました。単に全文検索できるようにするだけではなく、ドリルダウンやハイライトといった全文検索ならではの機能の実現方法も紹介しました。
Groongaを使いたいけど学習コストが増えそうだなぁと思っていた人は試してみてください。実際に試してみて詰まった場合や、ここには書いていないこういうことをしたいけどどうすればいいの?ということがでてきた場合は以下の場所で相談してください。
Groongaを用いた全文検索アプリケーションの開発に関するご相談は問い合わせフォームからご連絡ください。
Groonga関連の開発・サポートを仕事にしたい方は採用情報を確認の上ご応募ください。
2016-12-26時点でのDebian GNU/Linux sidでの話です。
以前、GNU Autotoolsを使ってWindows用バイナリーをビルドする方法を紹介しましたが、ここで紹介するのはCMakeを使う方法です。
CMakeでクロスコンパイルする方法はcmake-toolchains(7)に説明があります。CMakeはクロスコンパイル用にCMAKE_TOOLCHAIN_FILE
という変数を用意しています。前述のドキュメントはこの変数を使う方法を説明しています。
ただ、ファイルを作るのは少し面倒なので、ここではコマンドラインオプションでWindows用バイナリーをビルドする方法を紹介します。
Debian GNU/Linux上でWindows用バイナリをビルドするためにはクロスコンパイルする必要があります。そのためにMinGW-w64を使います。MinGW-w64はGCCでWindows用バイナリをビルドするために必要なヘッダーファイルやライブラリなど一式を提供します。
cmakeパッケージとmingw-w64パッケージをインストールします。wineパッケージは動作確認用です。
% sudo apt install -V cmake mingw-w64 wine
準備はこれで完了です。
ここではCMakeでのビルドに対応しているGroongaをクロスコンパイルします。
まず、ソースコードをダウンロードして展開します。
% wget http://packages.groonga.org/source/groonga/groonga-6.1.1.tar.gz
% tar xvf groonga-6.1.1.tar.gz
% cd groonga-6.1.1
CMakeを使うときはソースコードのあるディレクトリーではなく別のディレクトリーをビルドディレクトリーにすることが多いですが、ここでは説明を簡単にするためにソースコードのあるディレクトリーを使います。
GNU Autotoolsのときは--host=x86_64-w64-mingw32
を指定するだけであとはいい感じに設定してくれますが、CMakeは次のようにいくつかのパラメーターを指定します。この中でクロスコンパイルに直接関係ないパラメーターはCMAKE_INSTALL_PREFIX
だけです。これはインストール先を変更するために指定しているだけです。
% PKG_CONFIG_LIBDIR=/tmp/local.windows/lib/pkgconfig \
cmake \
-DCMAKE_INSTALL_PREFIX=/tmp/local.windows \
-DCMAKE_SYSTEM_NAME=Windows \
-DCMAKE_SYSTEM_PROCESSOR=x64 \
-DCMAKE_C_COMPILER=x86_64-w64-mingw32-gcc \
-DCMAKE_CXX_COMPILER=x86_64-w64-mingw32-g++ \
-DCMAKE_RC_COMPILER=x86_64-w64-mingw32-windres
各パラーメーターについて説明します。
PKG_CONFIG_LIBDIR
: pkg-config
が.pc
ファイルを検索するときに使うパス。通常は(LIBDIR
ではなく)PKG_CONFIG_PATH
を指定するが、クロスコンパイルのときはPKG_CONFIG_LIBDIR
を指定して、ビルド環境(この場合はDebian GNU/Linux)の.pc
ファイルを一切検索しないようにする。CMAKE_SYSTEM_NAME
: Windows
を指定するとCMakeに組み込まれているWindows用のビルドの設定を使ってくれる。(/usr/share/cmake-3.7/Modules/Platform/Windows.cmake
などを使ってくれる。)CMAKE_SYSTEM_NAME
を設定するときはあわせてCMAKE_SYSTEM_VERSION
も設定するべきだが、MinGW-w64でクロスコンパイルするときはCMAKE_SYSTEM_VERSION
を使っていなそうなので省略している。CMAKE_SYSTEM_PROCESSOR
: 32bitのバイナリーをビルドするか64bitのバイナリーをビルドするかを指定する。32bitならx86
で64bitならx64
になる。ここではx64
を指定しているので64bitのバイナリーをビルドする。CMAKE_C_COMPILER
: MinGW-w64が提供するgcc
のコマンド名を指定する。PATH
が通っていない場合はフルパスで指定する。CMAKE_CXX_COMPILER
: MinGW-w64が提供するg++
のコマンド名を指定する。PATH
が通っていない場合はフルパスで指定する。CMAKE_RC_COMPILER
: MinGW-w64が提供するwindres
のコマンド名を指定する。PATH
が通っていない場合はフルパスで指定する。.rc
ファイルを使わない場合は指定しなくても大丈夫。CMAKE_FIND_ROOT_PATH_MODE_PROGRAM
なども指定した方が間違ってビルド環境にあるプログラムやライブラリーなどを見つけてしまわなくてよいのですが、Groongaのようにpkg-config
を使っている場合は設定しなくても大丈夫です。
あとはmake
でビルドできます。
% make
make install
するとCMAKE_INSTALL_PREFIX
で指定したディレクトリー以下にインストールされます。
% make install
Wineを使うとDebian GNU/Linux上でインストールしたバイナリーで動作確認できます。Groongaのようにg++
を利用している場合はlibstdc++-6.dll
とlibgcc_s_seh-1
(seh
はStructured Exception Handlingの略)がないとバイナリーを実行できません。動作確認する前に${CMAKE_INSTALL_PREFIX}/bin/
以下にこれらのDLLをコピーします。
% cd /tmp/local.windows/bin
% cp $(x86_64-w64-mingw32-g++ -print-file-name=libstdc++-6.dll) ./
% cp $(x86_64-w64-mingw32-g++ -print-file-name=libgcc_s_seh-1.dll) ./
これで動作確認できます。
% wine groonga.exe --version
Groonga 6.1.1 [Windows,x64,utf8,match-escalation-threshold=0,nfkc,onigmo]
configure options: <>
CPackを使えばビルドしたバイナリーを含むアーカイブを作成することができます。PGroongaはCPackを使ってバイナリー入りアーカイブを作成しています。
CMakeLists.txt
にCPackの設定がある場合は次のようにすればアーカイブを作成できます。
% make package
Groongaを例にしてDebian GNU/Linux上でCMakeを使ってWindows用のバイナリーをビルドする方法を紹介しました。CMakeを使っているプロダクトをクロスコンパイルしたいときは参考にしてください。
CentOS 7ではPolicyKitによりユーザーの権限昇格・拒否の方法を柔軟に指定することができます。 実際に、CentOS 7では/etc/polkit-1/rules.d/50-default.rulesにより、管理者の認証が必要になった時に必要となるメソッドを追加しています。
/* -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- */
// DO NOT EDIT THIS FILE, it will be overwritten on update
//
// Default rules for polkit
//
// See the polkit(8) man page for more information
// about configuring polkit.
polkit.addAdminRule(function(action, subject) {
return ["unix-group:wheel"];
});
これにより、CentOS 7ではwheelグループにいるユーザーはrootのパスワードを用いることなく各自のパスワードにて管理操作用に認証されるようになりました。
CentOS 7では従来通りの.pkla, .confを用いる方法も引き続き利用できます。
PolicyKitを用いて適切に権限管理するにはまず、void polkit.addRules(polkit.Result function(action, subject){..})
と void polkit.addAdminRules(string[] function(action, subject){..})
の働きを知る必要があります。他にもいくつか関数がありますが、ここでは解説しません。
返り値を見ると両方ともvoidとなっていることから内部で副作用を起こす関数となっていることが推測されます。
まず、一つ目の関数の polkit.addRules(polkit.Result function(action, subject){..})
ついてはあるアクション(subject)に対する認証・拒否の設定を行います。polkit.Result定数は以下から選択してfunctionオブジェクトの返り値とします。
polkit.Result = {
NO : "no", // 無条件で拒否、アクションは実行されません。
YES : "yes", // 無条件で認証、アクションを実行します。
AUTH_SELF : "auth_self", // 認証が必要だが管理者ユーザーの必要なし。
AUTH_SELF_KEEP : "auth_self_keep", // 認証が必要だが管理者ユーザーの必要なし。一定期間後無効。
AUTH_ADMIN : "auth_admin", // 認証が必要で管理者ユーザーの必要あり。
AUTH_ADMIN_KEEP : "auth_admin_keep", // 認証が必要で管理者ユーザーの必要あり。一定期間後無効。
NOT_HANDLED : null // このルールでは何もしない
};
これらの値のうちどれかをfunctionオブジェクトの返り値にすることになります。
二つ目の void polkit.addAdminRules(string[] function(action, subject){..})
のfunctionオブジェクトではユーザーグループ、または、ユーザー名とその対になる文字列の配列を返すことになります。
例えば、はじめにの節で例に挙げていた /etc/polkit-1/rules.d/50-default.rules では以下のように使われています。
polkit.addAdminRule(function(action, subject) {
return ["unix-group:wheel"];
});
これはつまり管理者としての認証を求められた時にはwheelグループに属するユーザーのパスワードを用いることで管理者として振る舞えるようにします、というルールを追加しています。
PolicyKitのルールはファイル名の辞書順に読み込まれるため、rulesファイルで優先して読み込みたいルールがある場合は10や20などの小さい数字をファイル名の先頭へつけると良いでしょう。
ここで、PolicyKitの権限管理のあらましを解説したところで実例を見ていきます。 シャットダウン・再起動などの電源に関わる操作について権限管理してみます。
GNOMEデスクトップ環境からwheelに属さない一般ユーザーに対してシャットダウン・再起動などの電源に関わる操作をGNOMEデスクトップ上での操作を禁止することを目標にルールを定めてみることにします。
まず、対象となるアクションIDを知る必要があります。 freedesktop.orgのlogindの項目 によると、シャットダウン・再起動などの電源に関わる操作については以下のアクションIDが該当します。
アクションIDがわかったところで、ルールを作成しします。 これらのルールをまとめると以下のようになります:
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.login1.power-off"
|| action.id == "org.freedesktop.login1.power-off-multiple-sessions"
|| action.id == "org.freedesktop.login1.power-off-ignore-inhibit"
|| action.id == "org.freedesktop.login1.reboot"
|| action.id == "org.freedesktop.login1.reboot-multiple-sessions"
|| action.id == "org.freedesktop.login1.reboot-ignore-inhibit"
|| action.id == "org.freedesktop.login1.suspend"
|| action.id == "org.freedesktop.login1.suspend-multiple-sessions"
|| action.id == "org.freedesktop.login1.suspend-ignore-inhibit"
|| action.id == "org.freedesktop.login1.hibernate"
|| action.id == "org.freedesktop.login1.hibernate-multiple-sessions"
|| action.id == "org.freedesktop.login1.hibernate-ignore-inhibit")
&& subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
else {
return polkit.Result.NO;
}
});
これを、 /etc/polkit-1/rules.d/55-disallow-power-related-rules-for-general-user.rules のような名前で保存します。その後、polkitを止め、再度起動させます。
$ sudo systemctl stop polkit
$ sudo systemctl start polkit
/var/log/messagesのようなsyslogのログに以下のようなログが出ていれば登録に成功しています。
Dec 26 16:37:50 localhost systemd: Stopped Authorization Manager.
Dec 26 16:37:50 localhost systemd: Starting Authorization Manager...
Dec 26 16:37:50 localhost polkitd[4129]: Started polkitd version 0.112
Dec 26 16:37:50 localhost dbus[686]: [system] Successfully activated service 'org.freedesktop.PolicyKit1'
Dec 26 16:37:50 localhost dbus-daemon: dbus[686]: [system] Successfully activated service 'org.freedesktop.PolicyKit1'
Dec 26 16:37:50 localhost systemd: Started Authorization Manager.
Dec 26 16:37:51 localhost gnome-session: PolicyKit daemon reconnected to bus.
このルールが適用されているかどうかを確認してみます。 一般ユーザーのnormaluser1ユーザーを作成します。
その後、GNOME環境にログインすると、シャットダウンや再起動操作関連のUIがなくなっていることがわかります。
一般ユーザーの電源管理画面:
wheelユーザーの電源管理画面:
PolicyKitのルールにより、一般ユーザーの電源管理の操作を禁止する実例を提示しました。このPolicyKitの仕組みはKDEでも一部利用されています。 この他利用用途としては、特権が要求される操作に対してのルールを作成し、一般ユーザーから操作できるようにすることもできます。 例えばドライブのマウントはハードウェアが絡むため、この操作はLinuxでは通常、管理者特権が要求されます。 これに対してもPolicyKitのルールにより一般ユーザーから操作可能にすることができます。例えば、udisksに対するPolkitのアクションのリストはここを参考にすると良いです。