アペフチ

リポジトリーをGitHubからGitLabに移してみた

EPUB Parser gemのリポジトリーを、GitHubからGitLabに移してみた:
https://gitlab.com/KitaitiMakoto/epub-parser

ここのところフリーとかオープンソースソフトウェアとサービスとかについて感じるところの記事を書いていて(未公開)、その過程でGitLabのことを思い出したので、やってみたのだった。

前にクローンはしていたのだけど、masterブランチへのフォースプッシュが禁止されていたので、使わなくなっていた。でも今見てみたら、設定によりそれが可能になっていたので、改めて移行した。

GitHubはTravis CICircleCIなんかとのインテグレーションが便利だけど、GitLab.comは自前でCI機能を備えていて、YAMLファイル(.gitlab-ci.yml)をリポジトリーに入れておくと、その他の設定なしで自動でテストを走らせてくれる。べんり。

before_script:
  - apt-get update -qq && apt-get install -y zip
  - ruby -v
  - which ruby
  - gem install bundler --no-document
  - bundle install --jobs=$(nproc) "${FLAGS[@]}"

test:2.2:
  image: ruby:2.2
  script:
    - bundle exec rake test

test:2.3:
  image: ruby:2.3
  script: bundle exec rake test

test:2.4:
  image: ruby:2.4
  script: bundle exec rake test

GitLabとは

GitLabは、Gitを使ったプロジェクトのホスティングウェブアプリケーションで、ざっくり言ってGitHubクローンだ。基本的な部分(Community Edition)のソースコードは公開されていて、自前のサーバーにインストールして使うことができる。Enterprise Editionが有償になっている。その他にホスティングサービス(GitLab.com)もやっていて、これを今回は使った。

Gitリポジトリーのホスティングの他、READMEの表示、スター、フォーク、マージリクエスト(プルリクエスト)、イシュー登録、コミット履歴表示、コミット毎の差分表示、ウィキ、グループによるメンバー管理、(GitHub Pagesのような)静的ウェブサイトのホスティング、(Gistのような)スニペットなどなど、GitHubの基本的な機能は備えている(使い勝手や速度で及ばないところはある)。

ちょっとググった感じだと自前サーバーでGitのプロジェクトをホストするために使われる例が多いようだけど、GitHub.comの代替として使ってもいいと思う。僕は、アプリケーションのソースコードが公開されているということで、使い始めてみた。前はGitoriousが担っていた立ち位置だと思う(事実、Gitoriousがシャットダウンする時に、GitLabを案内していた)。

この日記はGitHub Pagesのドメインで公開しているので、移行できない。

追記:GitLab参考記事

奇しくも最近、1/5に、Gihyo.jpでGitLabの記事が上がっていた:
GitLabのこれまでとこれから:新春特別企画|gihyo.jp … 技術評論社

アドカレ #コルクおすすめ2016 24日目 センター試験直前に、三田紀房『ドラゴン桜』後半

アドベントカレンダー「年末年始おすすめ作品 BY.CORK Advent Calendar 2016」の24日目です。

さて、アドベントカレンダー、クリスマスイブ担当という大役を仰せつかってしまったが(単に人がいなかっただけだが……くそっ、リア充どもめ)、実のところ、この日に相応しい作品が思い付かない。ほぼ唯一の持ちネタである今敏の『東京ゴッドファーザーズ』は、21日担当のまつおかさんに取られてしまった。

ディケンズの『クリスマスキャロル』も定番で外れが無いと思うのだが、そう言えば、半分くらいまで読んで止まっているのだった。日本に寄せて恋愛ものを選ぶとしても、僕が好きな恋愛ものジャンルは浮気・不倫なので、こんな日に相応しくない(いや、ある意味、リアルで、相応しいかも知れんが……)。

ということでクリスマスイブっぽいのは諦めました。アドベントカレンダーの趣旨に戻って、年末年始におすすめする物を選びます。

三田紀房『ドラゴン桜』

ドラゴン桜 (1)

年末年始特に意識すると言えば、センター試験だ。僕は塾の先生から、休憩を取ることの大切さ、取るタイミングについて教えてもらって、守っていた。年末年始はきちんと休んだし、試験の前日も勉強は一切せず、のんびり、気になっていた本を読んでいた(その代わり他の日は頑張って勉強していて、特に嫌いな英語は、このままやってたら気が狂いそうだと自分で感じていた……)。大工の祖父が家の車庫にバスケットゴールを取り付けてくれたので、毎日、休憩がてら、ひたすらシュートしていた(そのお陰で、文科系部活のくせに、体育のバスケットボールでめっちゃ得点してた)。

このやり方が他の人にも合うかはもちろん分からないが、「この時期にはもうメンタル面が非常に大きなウェイトを占める」「メンタルは身体の影響を強く受ける」「なので休憩などを上手に(好きなだけ、ではない)取ることがとても大事」という点については衆目の一致が見られるんではないかと思う(未確認)。

その時期のドラマが、三田紀房の『ドラゴン桜』で描かれている。テレビ版は観ていないのと、この作品について誰かと話したこともないので、世間一般のイメージは分からないけれど、僕は「一見破天荒な受験テクニックを色々紹介しているまんが」という印象で記憶していた。ところが、コルクに入社するにあたって読み直してみたら(作者の三田紀房さんのエージェントをしています)、後半に入ると、精神面をどう受験に向けていいコンディションに持っていくかという話がメインだった。心や気持ちの問題だし、自分が通過してきたことで感情移入もしやすく、共感さるシーンが多い。

特に好きなのが、二人いる主人公の一人、矢島が、父親の車に乗るところ。父親のことを嫌っておりほとんど話さない矢島だけど、先生のアドバイスもあって、少し心を開きかけ、でもまた悶着あって忌避感を覚えていた頃、たまたま、家を出るタイミングが父親とかぶる。車に乗れ、送って行くと言う父親。黙って乗る矢島。そして、降りるまで、一言も話さない。「降り際に何か一言くらい言うのかな」と思って読んでいたけれど、どちらも何も発さない。コミュニケーションのないまま、車のシーンは終わる。ここでものすごく「そうだよね!」という気持ちになり、その気持ちが何なのか分からないせいで、今に至るまで記憶に残っているのだ。

こういう、言葉にならない気持ちを抱かせるシーンが後半には色々と出てくるのでぜひ、これから受験する人、受験したことある人に読んでほしい。ただ、そこに至るまで10巻くらいを費やしていて、上に三巻までの試し読みを貼りはしたけど、正直、前半は飛ばして10巻、11巻くらいから読むのが、(年末年始のタイミングでは)いいのではないかと思ってる。

最後に、自社のことなので宣伝。三田紀房さんの公式サイトはこちら。『ドラゴン桜』にちなんで受験に関する記事なんかも載っています。

三田紀房公式サイト - http://mitanorifusa.com/

明日の担当は中山マリモさん、アドカレの予告には「ラブ・マスターX/安野モヨコ」と書いてあって、ほう……。

Railsでの検索機能にgroonga-client-railsを使う(後編)

アドベントカレンダー「Groonga Advent Calendar 2016」の21日目です、書いているのは25日ですが……済みません

前回のRailsでの検索機能にgroonga-client-railsを使う(前編)では、groonga-client-rails gemを使って

  • Groongaのデータベースを作ること
  • Railsのモデル操作とGroongaデータベースの同期を取ること

をやりました。

後編の今日は、Groongaデータベースを使って、Railsアプリに検索機能を付けてみようと思います。

引き続きアプリケーションのリポジトリーをGitHubに置いています:KitaitiMakoto/groonga-client-rails-sample

目次

  1. ルーティングの追加
  2. 検索アクションの追加
  3. 検索結果の表示
  4. 検索フォームの作成
  5. 検索語のハイライト
  6. 高度な検索(カラムの指定、並び替え、ページネーション)

ルーティングの追加

検索用のルーティングを追加します。

  • posts?q=xxxと既存のコレクションリソースを使ってクエリーで検索機能を呼び出す
  • posts/searchと検索専用のリソースを追加する

の二通りあり、アプリケーション全体のデザインで選ぶべき物だと思いますが、ここでは後者にします。

# config/routes.rb

resources :posts do
  collection do
    get :search
  end
end

検索アクションの追加

PostsControllerに検索アクションを追加します。

# app/controllers/posts_controller.rb

def search
  searcher = PostsSearcher.new
  query = params[:q]
  if query.blank?
    redirect_to action: "index"
    return
  end
  @posts = searcher.search.
             query(query).
             result_set.records
end

(モデルの代わりに)サーチャークラスをインスタンス化し、クエリーを組み立てていきます。

検索の開始には#searchメソッドを呼び出します。これでクエリー組み立ての準備が整います(クエリーオブジェクトが返されます)。

queryメソッドに文字列を渡すことで、検索語を認識させます。

result_setを呼ぶとリモートのGroongaサーバーにHTTPリクエストを送って検索結果を取得します。

recordsによって、それをRubyのオブジェクトに変換して返します。

検索結果の表示

検索結果を表示します。app/views/posts/index.html.erbapp/views/posts/search.html.erbにコピーし、ActiveModel依存の所を書き換えます。

<!-- app/views/posts/search.html.erb -->

<h1>Posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Body</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.body %></td>
        <td><%= link_to 'Show', post_path(extract_id(post)) %></td>
        <td><%= link_to 'Edit', edit_post_path(extract_id(post)) %></td>
        <td><%= link_to 'Destroy', post_path(extract_id(post)), method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>

<br>

<%= link_to 'New Post', new_post_path %>

extract_idは、レコードデータからIDを取り出すヘルパーです。これも自分で定義します。

module PostsHelper
  def extract_id(post)
    post["_key"].split("-")[1]
  end
end

Groongaでは、_keyというカラムによって、レコードを一意に特定するのですが、groonga-client-rails(のデフォルト)では、「モデル名-連番」となる(Post-1)ので、そこからAcitiveRecordのIDに変換しています。

検索フォームはありませんが、これで一応機能はできました。http://localhost:3000/posts/search?q=Qiita などにアクセスすると、検索結果が見られると思います。

検索フォームの作成

次にフォームです。/posts/searchqクエリー付きでGETアクセスを投げるだけなので簡単です。

<!-- app/views/posts/_search_form.html.erb -->

<%= form_tag(search_posts_path, method: "get") do %>
  <input type=search name=q value="<%= params[:q] %>" required>
  <%= submit_tag("Search") %>
<% end %>

これをそれぞれのテンプレートファイルに埋め込んでやります(省略)。

検索語のハイライト

ただ、せっかくだから、検索語が分かりやすくなっていてほしいですよね。また、メモの全文をここで表示してしまうと、長過ぎるという場合もあると思います。両方をいっぺんに解決できる方法として、Groongaのsnippet_html関数があります(7.14.17. snippet_html)。

これは検索語の周辺数十文字(スニペット)を返してくれる関数です。更に、検索語を<span class="keyword">...</span>でマークアップしてくれます(HTML)。

snippet_htmlを使うには、Groongaから取得するカラムにこれを指定します。groonga-client-railsはデフォルトで、モデルで設定したカラムを取得してくれます(なのでtitlebodyが取れていた)。これをカスタマイズするには、クエリーにoutput_columnsというパラメーターを追加する必要があります(7.3.54.4.4.1. output_columns)。

# app/controllers/posts_controller.rb

  @posts = searcher.search.
             query(query).
             output_columns('_key,title,snippet_html(body)').
             result_set.records

これで、「bodyカラムでの検索結果にはスニペットを取得する」という意味になります。

これに合わせてビューも変えなくてはいけません。

        <td><%== post.snippet_html.join("<br>") %></td>

HTMLを埋め込むので===にしています。また、結果は配列になっているので(一つのメモの離れた所に検索語がある場合、それぞれの周辺を取得します)改行で接続します。

先述の通り、検索語はマークアップされるので、スタイリングしましょう。

/* app/assets/stylesheets/posts.scss */

.keyword {
  font-weight: bolder;
  color: red;
}

これで、検索語が赤い太字になりました。

何とか見られる結果になったんではないでしょうか。

Groongaでは、検索の際に様々な条件を付け加えたり、結果を加工したりできます。機能の詳細はドキュメント(7.3.54. select)に譲りますが、ここでは以下の三つに対応してみましょう。

match_columns
検索に使用するカラムを試定。例えば「検索語がタイトルに含まれる場合のみ表示する」など。
sortby
指定したカラムで並び替える。
paginate
検索結果が多過ぎる場合にページネーションします。

と言っても簡単で、クエリーオブジェクトから、それぞれのメソッドを呼び出すだけです。

# app/controllers/posts_controller.rb

  @posts = searcher.search.
             query(query).
             output_columns('_key,title,snippet_html(body)')
  [:match_columns, :sortby, :paginate].each do |param|
    if params[param].present?
      @posts = @posts.send(param, params[param])
    end
  end
  @posts = @posts.result_set.records

これにフォームを対応させれば出来上がりです。

例えば、タイトルで並び替えした場合はこうなります。

並び順を逆にするには、カラム名の前にマイナス記号(-)を付けます。

どうでしたか、groonga-client-railsは、無理にActiveModel風にしない所が気に入っていたりします……と言っている間に、開発者の@kouさんがよりちゃんとした記事を書いていました、締め切り破って済みませんでした……。

» Ruby on RailsでMySQL・PostgreSQL・SQLite3とGroongaを使って日本語全文検索を実現する方法