Try ActiveGroonga!

投稿者 okkez 2010-12-05 07:04:00 GMT

(この記事は Ruby Advent Calendar jp: 2010 : ATND の 5日目です。前日はauthorNariさんでした。)

buzztter - Twitter のイマを切り取ったー☆Rubyリファレンスマニュアル全文検索 | るりまサーチ で使われている groonga - an open-source fulltext search engine and column store. をご存知でしょうか。groonga 自体の説明は公式サイトを見てください。

groonga には Ruby バインディングの rroonga があります。 さらにその rroonga を ActiveRecord っぽいインターフェイスで使える ActiveGroonga があります。

ところで、先日 2010-11-29 に 全文検索エンジンgroongaを囲む夕べ #1 : ATND がありました。 その中で Ruby 枠の話もありました。

Ruby 枠の最後の方で Rails3 と ActiveGroonga を使ったデモがあったのです が、それを自分なりに再構成してみたいと思います。

開発環境は groonga が 64bit 前提で、rroonga が Ruby1.9.2 必須です。

テーマは某ミニブログのクローンです。 名前は groonga を使っているのでちょっと長いですが groongatter にしましょう。 まずは、新しいプロジェクトを作成します。ActiveRecord と prototype.js は使わないので、このプロジェクトには組み込みません。

  $ rails new groongatter -O -J

Gemfile を編集して ActiveGroonga を使うようにします。

source 'http://rubygems.org'

gem 'rails', '3.0.3'

gem 'activegroonga'

編集したら以下のコマンドを実行します。

  $ bundle install --path vendor/bundle

rroonga のインストール時に groonga の適切なバージョンが見つからない場 合は groonga もビルドするので時間がかかります。rroonga や groonga のビ ルドに失敗したときは groonga のビルド環境を整えてからやってみてくださ い。Debian や CentOS のパッケージは用意されているのでそちらをインストー ルしてからやってみるのもいいでしょう。

次に config/application.rb を編集しておきましょう。

diff --git a/config/application.rb b/config/application.rb
index 1c1e1aa..296a1d5 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -7,6 +7,8 @@ require "action_mailer/railtie"
require "active_resource/railtie" require "rails/test_unit/railtie"
+require "active_groonga/railtie"
+
# If you have a Gemfile, require the gems listed there, including any gems # you've limited to :test, :development, or :production. Bundler.require(:default, Rails.env) if defined?(Bundler)

これで開発準備が整いました。

まずは、ユーザモデルを作ってみましょう。

  $ rails generate scaffold user name:short_text
  $ rake groonga:migrate
  $ rails server

ウェブブラウザで http://localhost:3000/users にアクセスすると、いつもの scaffold な画面が表示されるので適当に何人かユーザを作成しておく。

次につぶやきモデルを作ります。

  $ rails generate scaffold tweet content:short_text user:reference

http://localhost:3000/tweets から適当につぶやいてみてください。 このとき User には既に存在する User の id を指定してください。

このままだと、表示が #<User:0x00000003bd9388> のようになってしまうので view を修正します。とりあえず、 views/tweets/{index,show}.html.erb あ たりを修正しておきましょう。

<h1>Listing tweets</h1>

<table>
  <tr>
    <th>User</th>
    <th>Content</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @tweets.each do |tweet| %>
  <tr>
    <td><%= tweet.user.name %></td>
    <td><%= tweet.content %></td>
    <td><%= link_to 'Show', tweet %></td>
    <td><%= link_to 'Edit', edit_tweet_path(tweet) %></td>
    <td><%= link_to 'Destroy', tweet, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

<br />

<%= link_to 'New Tweet', new_tweet_path %>
<p id="notice"><%= notice %></p>

<p>
  <b>User:</b>
  <%= @tweet.user.name %>
</p>

<p>
  <b>Content:</b>
  <%= @tweet.content %>
</p>

<%= link_to 'Edit', edit_tweet_path(@tweet) %> |
<%= link_to 'Back', tweets_path %>

やっと全文検索用のインデックスを作ります。

  $ rails generate migration create_full_text_search_index 

こんな感じでインデックスを定義します。 オプション説明は groonga のドキュメントなどを見るといいと思います。

class CreateFullTextSearchIndex < ActiveGroonga::Migration
  def up
    create_table("bigram",
                 :type => :patricia_trie,
                 :key_type => "ShortText",
                 :default_tokenizer => "TokenBigram",
                 :key_normalize => true) do |table|
      table.index("tweets.content")
    end
  end

  def down
    remove_table("bigram")
  end
end
  $ rake groonga:migrate

とりあえず、コンソールで試してみましょう。

  $ rails console
  >> result_set = Tweet.select(:content => "こん")
  >> pp retult_set.first
  => #<Tweet id: 1,
  user: #<User id: 1, updated_at: 1970-01-01 09:00:00 +0900, name:
  "okkez", created_at: 2010-12-05 15:13:55 +0900>,
  updated_at: 2010-12-05 15:14:58 +0900, created_at: 2010-12-05 15:14:44 +0900, content: "こんにちは!">

うまく検索出来ているみたいですね!

最後に検索用の UI を作っておきましょう。

  $ rails generate controller search index

config/routes.rb はこんな感じにしておきます。

Groongatter::Application.routes.draw do
  match "search", :to => "search#index", :as => "search"

  resources :tweets

  resources :users
end

app/controllers/search_controller.rb はこんな感じです。

class SearchController < ApplicationController
  def index
    @query = params[:query]
    if @query.blank?
      @result_set = []
    else
      @result_set = Tweet.select do |entry|
        entry.content =~ @query
      end
    end
  end
end

app/views/search/{index,_form}.html.erb はそれぞれこんな感じです。

<h1>Search</h1>

<%= render :partial => "form" %>

<%- unless @query.blank? -%>
<p>"<%= @query %>" で検索した結果は <%= @result_set.count %> 件です。</p>
<%- end -%>

<%- unless @result_set.blank? -%>
  <ul>
    <%- @result_set.each do |entry |-%>
      <li><%= entry.user.name %>: <%= entry.content %></li>
    <%- end -%>
  </ul>
<%- end -%>
<typo:code>

<typo:code lang="html">
<%= form_tag(search_path) do %>
  <p>
    <%= text_field_tag(:query, "", :size => 25) %>
    <%= submit_tag("検索") %>
  </p>
<%- end -%>

あとはスペース区切りでの AND 検索くらいに対応したいところですが、それ は自分で試してみてください。

カテゴリ  | タグ  | コメントなし | トラックバックなし

Reading "Refactoring Ruby Edition" #2

投稿者 okkez 2010-07-12 12:43:00 GMT

日曜日に「リファクタリング Ruby エディション」読書会の第二回に行ってきた。 二章から六章の途中まで読んだ。

二章から五障まではオリジナルのリファクタリングとあまり違いが無かった。 六章からリファクタリングのカタログが始まる。まだいくつかの節しか読んでいないが、参考になることも多いけど、 知っていることの方が多い。

でも、間違ったことも色々と書いてあるので、読みこなすには中級以上の知識が必要だと思った。

カテゴリ  | タグ  | コメントなし | トラックバックなし

Reading "Refactoring Ruby Edition"

投稿者 okkez 2010-05-31 14:40:00 GMT

リファクタリングRubyエディションの読書会に行ってきた。

まだ第一章しか読んでいないけど、一章は間違いが多すぎる。 まず、サンプルで紹介されているコードが徹頭徹尾動かない。一回も実行していないレベル。

動かない部分を直して、GitHub にアップしました。 他の勉強用のコードも混じってるけど、本の流れに沿ってリファクタリングして最低限動くようにした。

ログメッセージが全部 refactor になってるのはご愛嬌です。

第一章は残念な感じでしたが、二章以降に期待したいところです。

カテゴリ  | タグ  | コメントなし | トラックバックなし

HikiDoc plugin

投稿者 okkez 2010-05-25 13:53:00 GMT

HikiDoc - FrontPage.ja でプラグインを使いたかったので、プラグインを作る方法を調べてみた。

ソースを読めばプラグインを作る方法はわかる。 しかし、HikiDoc を使った上でプラグインも使っている例はあまりないようだった。

class CustomOutput < HikiDoc::HTMLOutput
  def initialize(suffix = " />", options = {})
    super suffix
    @options = options
  end

  def inline_plugin(src)
    # あんまり使わないので未実装
  end

  def block_plugin(src)
    name, *args = parse_plugin(src)
    klass = Plugins.const_get(name.classify)
    @f.puts klass.new(@options).call(args)
  rescue NameError => ex
    @f.puts ex.message
  end

  private
  def parse_plugin(src)
    result = []
    if /([a-z0-9_]+)\((.+)\)/m =~ src
      result.push $1
      result.push *$2.split(',').map(&:strip) if $2
    end
    result
  end
end

こんな感じで HikiDoc::HTMLOutput を継承して inline_plugin と block_plugin を定義すれば良い。 今回の実装方法でプラグインを定義するには以下のようにする。 Rails で使用するのでヘルパーを使えるようにしておく。

module Plugins
end
class Plugins::Base
  include ActionView::Helpers, ::ERB::Util

  def initialize(options = {})
    @options = options
  end

  def call(args)
    raise 'must implement in subclass'
  end
end
class Plugins::Echo < Plugin::Base
  def call(args)
    %Q!<pre>#{args.inspect}</pre>!
  end
end

クラスを使わずに実装する方法もあるが、今回は Rails なので Rails っぽくなるようにクラスを使ってみた。 クラスを使わない場合は、プラグインの名前をキーにしたハッシュに処理を登録する方法が使えると思う。

Hiki では HikiDoc のプラグイン機構は使っていないように見えたけど気のせい?また今度調べてみる。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

using cancan

投稿者 okkez 2010-05-25 13:17:00 GMT

ryanb’s cancan at master - GitHub を使ってみた。

元々 be9’s acl9 at master - GitHub を使っていたのだけど、 ちょっと手の込んだことをやろうとすると、やり方がわからなくなったので cancan を使うことにした。 Rails Authorization in The Ruby Toolbox でも人気だったし @kakutani にも紹介してもらったし。

ほとんどのことは Home - cancan - GitHub の Wiki を見ればわかる。これで分からない場合は rdoc.info :: cancan を見ればいいはず。

acl9 に比べていいところは

  • 権限に関するコードが一ヶ所に書けるところ
  • 権限だけのテストを簡単に書けるところ
  • 複雑な権限管理もそれなりにシンプルに書けるところ
  • たぶん DB に権限情報を書いてそれを読み込むように書いても使えるところ

良くないところは、権限のコードが長くなってしまうことくらい。 これはテストを書けば、特に問題にはならないかもしれない。 あるいは DB に権限データを登録するようにしてしまえば、シンプルになるだろう。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

using paperclip

投稿者 okkez 2010-05-25 13:00:00 GMT

thoughtbot’s paperclip at master - GitHub を使ってみた。

あるモデルにカラムを追加して使う方法の場合は Home - paperclip - GitHub をよく読んで使えば問題なさそうだった。

一つのレコードに複数の添付ファイルを持たせる場合に少しハマったので書いておく。

class Issue < ActiveRecord::Base
  has_many :attachments, :class_name => "::Attachment", :dependent => :destroy
end
class Attachment < ActiveRecord::Base
  belongs_to :issue
  has_attached_file :attachment
end

このようなクラス構成の場合、Attachment というモデルは paperclip で定義している Paperclip::Attachment と名前が 被るので Issue の定義で注意する必要がある。 上で書いているように、:class_name オプションに「フルパス」でモデルのクラス名を書いておけば良い。

あと paperclip では xxx_{file_name,content_type,file_size,updated_at} というカラムを用意すると、 モデルに xxx という名前のメソッドが定義されるので xxx の部分を長くしすぎるとちょっとコードが読みづらくなる。

実際に Attachment モデルに attachment_* という名前でカラムを用意したので以下のようなコードが頻出して気持ち悪くなったことがある。


link_to @attachment.attachment_file_name, @attachment.attachment.url

Attachment クラスでエイリアスを定義しておけば多少はマシになるかと思う。

カテゴリ  | タグ ,  | コメントなし | トラックバックなし

yield template in layout file by using erb

投稿者 okkez 2010-03-29 13:15:00 GMT

タイトルは適当。

Rails とか sinatra の tilt っぽい感じでレイアウトファイルに yield って書けるようにする方法がわかった。

レイアウトファイル。layout.html.erb

<html>
<head></head>
<body>
<%= yield %>
</body>
</html>

読み込むテンプレート。body.html.erb

<p> Hello! </p>

コード。template.rb

require 'erb'

class Template

  def initialize()
    erb = ERB.new(File.read('layout.html.erb'), nil, '-')
    erb.def_method(self.class, 'layout', 'layout.html.erb')
    erb = ERB.new(File.read('body.html.erb'), nil, '-')
    erb.def_method(self.class, 'body', 'body.html.erb')
  end

  def render
    layout{ body }
  end
end

puts Template.new.render

Template#initialize の引数を追加したりすればもう少し汎用的になると思う。 ERB#def_method の第二引数はメソッド名なのでメソッド名として使える文字列を指定しなければならない。

追記

以下のようにすると Rails の動作に少し近くなるはず。

class Template
  def render
    str = body()
    layout{ str }
  end
end

カテゴリ  | タグ  | コメントなし | トラックバックなし

using jQuery in Rails

投稿者 okkez 2010-02-20 02:00:00 GMT

最近 Rails で作っているウェブアプリケーションでは jQuery を使うようにしている。 なんとなくこうすれば良さそう、というのがわかってきたので書いておく。

google の jsapi を使う。 どこかのブログで見つけたコード片をちょっと変更したやつ。

module ApplicationHelper

  def google_jsapi_tag
    '<script type="text/javascript" src="http://www.google.com/jsapi"></script>'
  end

  def google_load_tag(name, version)
    %Q|<script type="text/javascript">google.load("#{name}", "#{version}");</script>|
  end

end

layout では以下のようにする。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
          "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
  <head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8" />
    <meta http-equiv="Content-Script-Type" content="text/javascript" />
    <meta http-equiv="Content-Style-Type" content="text/css" />
    <%= google_jsapi_tag %>
    <%= google_load_tag 'jquery', '1.3' %>
    <%= google_load_tag 'jqueryui', '1.7' %>
    <%= javascript_include_tag 'application' %>
    <%= yield :head %>
    <%- javascript_tag do -%>
      jQuery(function($){
        if (navigator.cookieEnabled) {
          $("#cookie_check").hide();
        } else {
          $("#cookie_check").show();
        }
        <%= yield :script %>
      });
    <%- end -%>
    <%= stylesheet_link_tag 'application' %>
    <%= stylesheet_link_tag 'jquery-ui-1.7.2.custom' %>
    <title><%= @title %></title>
  </head>
  <body>
    <!-- 略 -->
  </body>
</html>

こうしておくと view では以下のように書くことができる。

<%- content_for :script do -%>
$("a#hoge").click(function(event){ /* なんか処理 */});
<%- end -%>

global な名前空間を汚さないし、割と短く書けるし気に入っている。

カテゴリ ,  | タグ , , ,  | コメントなし | トラックバックなし

prawn with Rails2.3.5

投稿者 okkez 2010-02-12 04:00:00 GMT

prawn を Rails2.3.5 で使おうと思って調べてみたら、prawnto が使えなくなっていた。

仕方が無いので prawn 単体を Rails で使う方法を考えてみたので書いておく。

以下のコードをそれぞれのファイルに書く。

# config/environment.rb
config.gem "prawn"
config.gem "prawn-layout", :lib => "prawn/layout"
config.gem "prawn-security", :lib => "prawn/security"

config.gem “prawn” だけだと prawn/{layout,security} がちゃんと読み込まれないので別の場所で require する必要がある。

# config/initializers/mime_types.rb
Mime::Type.register "application/pdf", :pdf
# 適当なヘルパー
  def render_pdf
    pdf = Prawn::Document.new(:page_size => "A4")
    # フォントは環境に合わせる
    pdf.font "/usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf"
    yield pdf
    pdf.render
  end

以上を追加したら、割と簡単に PDF を出力することができるようになる。

コントローラーでこんな風に書いて、

class ReportsController < ApplicationController
  def show
    @report = Report.find(params[:id]
    respond_to do |format|
      format.html # show.html.erb
      format.pdf # show.pdf.erb
    end
  end
end

ビューでこう書けばよい。

# show.pdf.erb
<%=
render_pdf do |pdf|
  pdf.text "Hello, PDF world!"
end
%>

追記

本家(github)をよく見たら別の方法が書いてあった。 * Using Prawn in Rails - prawn - GitHub

どっちが良い方法なんだろう。 github に書いてある方法だとちゃんとダウンロードするファイル名を指定できるのが良い。

自分が書いた方法だと、view なので view で使用するデータの受け渡しが楽だけどファイル名の指定とかが面倒。 あと Emacs で適切なモードになってくれないのが気にくわない。

カテゴリ  | タグ ,  | 2 comments | トラックバックなし

そろそろるりまを使ってみませんか?

投稿者 okkez 2009-12-18 13:32:00 GMT

この記事は Ruby Advent Calendar jp: 2009 : ATND の 18 日目です。前日は @tad さん でした。

愛用しているものの一つに るりま があります。 みなさんはもうお使いですか?

まだの人は以下の説明を読んでぜひ使ってみてください。

利用方法はいくつかあるので順番に紹介していきます。

web ブラウザを使って見る方法

http://doc.okkez.net/ にアクセスします。3 カラムになっていて左から順番に、BitClust, Static HTML, Download となっています。BitClust はるりまプロジェクトで作ったリファレンスマニュアルを見るためのウェブアプリケーションです。 doc.okkez.net では nginx + thin で動かしています。nginx がキャッシュするようにしているのでまあまあの速さで動くと思います。

Static HTML はあらかじめプログラムで生成した HTML ファイルを置いています。ウェブアプリケーション版とは少しレイアウトが異なりますが内容は同じです。参照したいクラスやメソッドが既に決まっている場合に使うと高速に参照することができます。ウェブブラウザのページ内検索機能を使用すると快適に閲覧できるでしょう。

Download は月一回肉の日(毎月29日)に作成したスナップショットを置いています。http://www.ruby-lang.org/ja/man/archive/ にも同じものを置かせていただいています。

ダウンロードしたアーカイブを使用する方法

  • http://www.ruby-lang.org/ja/man/archive/
  • http://doc.okkez.net/archives/

どちらかから、ダウンロードしたアーカイブを適当な場所に展開します。 アーカイブの中には以下のようなものがあります。

  • bitclust/ ライブラリ
  • db-1_8_7/ Ruby1.8.7 向けのデータベース
  • db-1_9_1/ Ruby1.9.1 向けのデータベース
  • readme.html このアーカイブの説明
  • refe-1_8_7 Ruby1.8.7向けの refe
  • refe-1_8_7.cmd Windows 用の Ruby1.8.7 向けの refe
  • refe-1_9_1 Ruby1.9.1 向けの refe
  • refe-1_9_1.cmd Windows 用の Ruby1.9.1 向けの refe
  • server.exe Windows 用の実行ファイル
  • server.exy server.exe を作るための exerb のレシピファイル
  • server.rb *nix などで使用するスクリプト

データベースというのは、指定したバージョン向けに前処理を施したテキストファイル群のことです。

このディレクトリにパスを通して使うのが一番簡単ですが、適当なスクリプトを書いたりするのもいいでしょう。例えば、以下のような感じで書くといいのではないでしょうか。

#! /bin/sh
DOC_BASE=$HOME/ruby-refm
exec ruby -Ke -I $DOC_BASE/bitclust/lib bitclust/bin/refe.rb -d $DOC_BASE/db-1_8_7 "$@"

Windows な人は *.cmd を参考にしてバッチファイルを書くといいのではないでしょうか。

また server.exe というファイルがありますが、Windows を使っている人はこれをダブルクリックすると、ローカルで web server が起動して、自動的にウェブブラウザが起動してリファレンスマニュアルを表示します。ローカルで動いているのでネットワークにつながっていないときでも使用できます。

ダウンロードした chm ファイルを使う方法

  • http://www.ruby-lang.org/ja/man/archive/
  • http://doc.okkez.net/archives/

先ほどと同様に、ダウンロードします。 chm はバージョン別になっているので自分が使いたい方をダウンロードすればいいでしょう。1.8.7, 1.9.1 以外のバージョンを使いたい人は、自分で chm ファイルを作るといいでしょう。(ここでは説明しません)

Windows Vista 以降の人は、ダウンロードしたファイルをダブルクリックしたときに出てくる警告ダイアログのチェックボックスを外すとちゃんと見られるはずです。

Windows を使っていない人はそれぞれ自分の好きな chm viewer をインストールして使ってください。意外と色々あります。

まとめ

個人的におすすめなのはコマンドラインから使用する refe です。すごく速いです。 とりあえず使ってみてください。古いリファレンスには書いていないこともたくさん書いてあります。 足りない部分はありますが、古いリファレンスよりはかなり進化しています。使わないと損です。 そして、使ってフィードバックをください。みなさんのご協力をお待ちしています。

明日は、たこ焼き仮面の中の人ではないかと噂されているあの人が書きます!

カテゴリ  | タグ ,  | コメントなし | トラックバックなし