Railsで分かち書きしてみる

# 概要
Railsに慣れるために、適当な文字列を入力してPOSTするとMeCabする仕組みを作ります。

- ModelとしてRawStrとMeCabedStrを作成（RawStr 1:n MeCabedStr）
- 画面でRawStrを入力してPOSTすると、RawStrとMeCabedStrを保存（永続化）する
- 永続化した結果を改めて画面に表示する。

実行したときのイメージ画像を最後の方につけています。

# 環境
Windows10です。以下の記事で構築した環境を使用します。
[Windows10(64bit) で Ruby 2.2.3 + Rails 4.2.5](http://qiita.com/sasanquaneuf/items/068ab4e5940a790327ba)
[Windows10(64bit)のRubyでMeCabをつかう(Natto)](http://qiita.com/sasanquaneuf/items/e4e57b60d04b75d68f80)

# 構築
## 1. Modelの構築
データを保存する、つまり**永続化**する場合、Railsの世界では対応するModelを作るのが一般的な手法のようです。Railsのチュートリアルを読んだりして、以下のコマンドでModelを作ってみます。
### 1.1. rbファイルの生成

```text:MeCabedStrをつくるコマンド（rb_model）
C:\temp\sample>rails generate model MeCabedStr mhash:string seqno:integer surface:string feature:string
      invoke  active_record
      create    db/migrate/20160116072657_create_me_cabed_strs.rb
      create    app/models/me_cabed_str.rb
      invoke    test_unit
      create      test/models/me_cabed_str_test.rb
      create      test/fixtures/me_cabed_strs.yml
```

rspecはまだ入れていないので、ymlの単体テストケースができているようです。あまり気にしません。sqliteに関して特にセットアップしていない（つもり）でも、とりあえず物は出来上がるようです。
ファイル名が、キャメル扱いされてちょっと変なme_cabed_strとかになっていますが、あまり気にしないことにします。
上の定義を見てわかるとおり、MeCabedStrモデルは、次の要素を持ちます。

- **m**hash...文字列のハッシュキー（mojiretsuのmではなくMeCabのm）
- seqno...ハッシュキーごとの連番
- surface...単語そのもの
- feature...品詞

★フィールド名（アトリビュート名というのが正しい？）をhashにすると、**ActiveRecord::DangerousAttributeError**などというエラーがでます。なんてやつだ。。。それなら、最初からエラーにしてくれよ、と思いました。ちょっとモンスターユーザーの気持ちが分かりますね。

### 1.2. migration
rakeでmigrateします。

```text:MeCabedStrをつくるコマンド（migrate）
C:\temp\sample>bundle exec rake db:migrate
== 20160116072657 CreateMeCabedStrs: migrating ================================
-- create_table(:me_cabed_strs)
   -> 0.0017s
== 20160116072657 CreateMeCabedStrs: migrated (0.0031s) =======================
```
![sqlite.PNG](https://qiita-image-store.s3.amazonaws.com/0/94255/5f11dd6d-c52c-db82-f8e3-26574e56df23.png)
これ、実行する前はdevelopment.sqlite3のサイズは0KBだったので、普通に増えているように見えます。sqliteなので、ファイルを削除して実行すると、何事も無かったかのように5KBのファイルが作られます。

### 1.3. ちゃんと使えることの確認
**ActiveRecord::DangerousAttributeError**やその他のエラーが出ないことを確認します。

```text:insertするまでコマンド
C:\temp\sample>rails console --sandbox
Loading development environment in sandbox (Rails 4.2.5)
Any modifications you make will be rolled back on exit
irb(main):001:0> MeCabedStr.new
=> #<MeCabedStr id: nil, mhash: nil, seqno: nil, surface: nil, feature: nil, created_at: nil, updated_at: nil>
irb(main):002:0> m = MeCabedStr.new(mhash:"hash-ga-hairu-yotei", seqno:1, surface:"tesuto", feature:"meishi")
=> #<MeCabedStr id: nil, mhash: "hash-ga-hairu-yotei", seqno: 1, surface: "tesuto", feature: "meishi", created_at: nil, updated_at: nil>
irb(main):003:0> m.save
   (0.0ms)  SAVEPOINT active_record_1
  SQL (0.0ms)  INSERT INTO "me_cabed_strs" ("mhash", "seqno", "surface", "feature", "created_at", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["mhash", "hash-ga-hairu-yotei"], ["seqno", 1], ["surface", "tesuto"], ["feature", "meishi"], ["created_at", "2016-01-16 07:40:07.475194"], ["updated_at", "2016-01-16 07:40:07.475194"]]
   (0.0ms)  RELEASE SAVEPOINT active_record_1
=> true
```
とりあえず、INSERTも問題なくできるようです。SELECTも、次のとおり問題ないようです。

```text:selectしてみるコマンド
irb(main):004:0> MeCabedStr.all
  MeCabedStr Load (0.0ms)  SELECT "me_cabed_strs".* FROM "me_cabed_strs"
=> #<ActiveRecord::Relation [#<MeCabedStr id: 1, mhash: "hash-ga-hairu-yotei", seqno: 1, surface: "tesuto", feature: "meishi", created_at: "2016-01-16 07:40:07", updated_at: "2016-01-16 07:40:07">]>
```

### 1.4. 生文字列に対応するModelの追加
最初は色々と行儀の悪い書き方を考えていたのですが、トリッキーになりすぎてRailsのしきたりに沿っていなさそうなので、入力された生の文字列にあたるModelをきちんと作ることにしました。
具体的には、RawStrという次の形式のModelを作成します。

- **m**hash...文字列のハッシュキー（mojiretsuのmではなくMeCabのm）
- **m**digest...**入力文字列**（ハッシュではない！）

```text:RawStrをつくるコマンド
c:\temp\sample>rails generate model RawStr mhash:string mdigest:string
      invoke  active_record
      create    db/migrate/20160116081510_create_raw_strs.rb
      create    app/models/raw_str.rb
      invoke    test_unit
      create      test/models/raw_str_test.rb
      create      test/fixtures/raw_strs.yml
c:\temp\sample>bundle exec rake db:migrate
== 20160116081510 CreateRawStrs: migrating ====================================
-- create_table(:raw_strs)
   -> 0.0024s
== 20160116081510 CreateRawStrs: migrated (0.0055s) ===========================
c:\temp\sample>rails console --sandbox
Loading development environment in sandbox (Rails 4.2.5)
Any modifications you make will be rolled back on exit
irb(main):001:0> RawStr.new
=> #<RawStr id: nil, mhash: nil, mdigest: nil, created_at: nil, updated_at: nil>
```

本当はindexを付けたほうがよい気がしますが、indexは後でつけられるみたいなので、とりあえずつけないことにします。外部キー制約等も、つけなくても一応動くので、とりあえずつけないことにします。

## 2. Controller/Viewの構築：とりあえず生データ登録
### 2.1. rbファイルの生成
```text:コマンド
c:\temp\sample>rails generate controller RawStrs
      create  app/controllers/raw_strs_controller.rb
      invoke  erb
      create    app/views/raw_strs
      invoke  test_unit
      create    test/controllers/raw_strs_controller_test.rb
      invoke  helper
      create    app/helpers/raw_strs_helper.rb
      invoke    test_unit
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/raw_strs.coffee
      invoke    scss
      create      app/assets/stylesheets/raw_strs.scss
```
### 2.2. erbファイルの作成
```erb:app\views\raw_strs\new.html.erb
<%= form_for(@raw_str, :url => {:action => :new}) do |f| %>

  <%= f.label :mdigest %>
  <%= f.text_field :mdigest %>
  <%= f.submit "MeCab", class: "btn btn-large btn-primary" %>
  
<% end %>
```

```erb:app\views\raw_strs\create.html.erb
```

空のファイルを作らないと動かなかったので、とりあえず空のファイルを作りました。
なお、application.html.erbは次のようにしておきます。（特に修正しなくてよいと思いますが）

```erb:app\views\raw_strs\create.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Sample</title>
</head>
<body>

<%= yield %>

</body>
</html>
```

### 2.3. routingの編集
```rb:config\routes.rb
Rails.application.routes.draw do
  resources :raw_strs
  root 'raw_strs#index'
end
```
### 2.4. controllerの編集
```rb:app\controllers\raw_strs_controller.rb（暫定その１）
class RawStrsController < ApplicationController
  def new
    @raw_str = RawStr.new
  end
  def create
    @raw_str = RawStr.new
    @raw_str.mdigest = params[:raw_str][:mdigest]
    @raw_str.mhash = Digest::SHA256.hexdigest(@raw_str.mdigest)
    @raw_str.save
  end
end
```
本当はnewで簡単にやりたかったのですが、要素が足りない関係か、うまくいかなかったので、とりあえずバカみたいに代入をします。
これで、DBに怪しいデータが入るようになります。あとは、

- MeCabの結果を分割したデータを登録する
- 処理結果を見られるようにする

ようにします。

## 3. MeCabedStrの登録
### 3.1. gemの追加
Gemfileにnattoを追加します。nattoそのものの入れ方は、環境構築に関するリンク先を参照してください。

```rb:Gemfile
...
gem 'natto'
...
```

### 3.2. controllerの編集
2.4.で作ったファイルの中身をさっそく書き換えます。

```rb:app\controllers\raw_strs_controller.rb（暫定その２）
class RawStrsController < ApplicationController
  def new
    @raw_str = RawStr.new
  end
  def create
    @raw_str = RawStr.new
    @raw_str.mdigest = params[:raw_str][:mdigest]
    @raw_str.mhash = Digest::SHA256.hexdigest(@raw_str.mdigest)
    @raw_str.save
    seqno = 0
    natto = Natto::MeCab.new('-F%f[0]')
    natto.enum_parse(@raw_str.mdigest).each do |n|
      if !(n.is_bos? or n.is_eos?) then
        @me_cabed_str = MeCabedStr.new
        @me_cabed_str.mhash = @raw_str.mhash
        @me_cabed_str.seqno = seqno
        @me_cabed_str.surface = n.surface
        @me_cabed_str.feature = n.feature
        @me_cabed_str.save
        seqno += 1
      end
    end
  end
end
```
mecab(natto)に与える引数のフォーマットについては、
http://www.rubydoc.info/gems/natto/Natto/MeCab
の内容を参考にしています。f[0]は品詞です。

ここまでの内容で試すと、こんな感じになります。
**見た目**
![mecab1.PNG](https://qiita-image-store.s3.amazonaws.com/0/94255/feb0663a-9de1-b68d-b099-a698a074e370.png)
**SQLiteががんばっている様子**
![mecab2.PNG](https://qiita-image-store.s3.amazonaws.com/0/94255/ef8ae74a-a6ea-8384-2c23-d949a7b58870.png)
一応、先頭から順にseqnoが振られて、データが登録されていることがわかります。
1レコードにつき30msかかると、長い日本語を登録すると秒単位で時間がかかる、ということですね。ひえー。。
毎回トランザクションを開いて閉じているので、これをどうにかすれば良さそうです。
→コメントを頂いたので、4.1.に組み込みました。ありがとうございます。

## 4. よりよいViewをつくる
createした後にredirectさせるようにして、redirectした先でデータを拾わせるようにします。具体的には、controllerでshowにredirect＋redirectの処理を追加して、showに対応するviewを作ります。

### 4.1. controllerの編集
```rb:app\controllers\raw_strs_controller.rb
class RawStrsController < ApplicationController
  def new
    @raw_str = RawStr.new
  end
  def create
    RawStr.transaction do
      @raw_str = RawStr.new
      @raw_str.mdigest = params[:raw_str][:mdigest]
      @raw_str.mhash = Digest::SHA256.hexdigest(@raw_str.mdigest)
      @raw_str.save!
      seqno = 0
      natto = Natto::MeCab.new('-F%f[0]')
      natto.enum_parse(@raw_str.mdigest).each do |n|
        if !(n.is_bos? or n.is_eos?) then
          @me_cabed_str = MeCabedStr.new
          @me_cabed_str.mhash = @raw_str.mhash
          @me_cabed_str.seqno = seqno
          @me_cabed_str.surface = n.surface
          @me_cabed_str.feature = n.feature
          @me_cabed_str.save!
          seqno += 1
        end
      end
      redirect_to @raw_str
    end
  end
  def show
    @raw_str = RawStr.find(params[:id])
    @me_cabed_strs = MeCabedStr.where(mhash: @raw_str.mhash)
  end
end
```
最後の方に加わっているredirectとshowがポイントです。また、扱うデータが複数になるので、transactionを明示的に開始するようにしました。

### 4.2. erbファイルの作成

```erb:app\views\raw_strs\show.html.erb
<span style="font-weight:bold;"><%= @raw_str.mdigest %></span><br />
を分解してしまいました。<br />&nbsp;
<table>
  <tr><th>surface</th><th>feature</th></tr>
<% @me_cabed_strs.each do |t| %>
  <tr><td><%= t.surface %></td><td><%= t.feature %></td></tr>
<% end %>
</table>
```

この状態で、サーバを起動してブラウザから
http://localhost:3000/raw_strs/new
にアクセスしてみると…
![mecab3.PNG](https://qiita-image-store.s3.amazonaws.com/0/94255/12b61ada-1900-18bb-3719-d8b4f973185a.png)
![mecab4.PNG](https://qiita-image-store.s3.amazonaws.com/0/94255/57a1db16-62ac-6fdd-d4f3-7a8d17b50fbf.png)

分かち書きをして、かつデータが保存されるようになりました。

# 感想

- railsは「コマンドラインをたたきながらつくる」という感じがしました。コンパイルとか実行以外で。
- SQLiteのトランザクションを毎回開くのはかなり遅いので、どうにかしたいですね。（参考：http://uniunix.com/blog/?p=154）
- RSpecの部分をかなり無視したので、これをそのうちやろうかなー。
- http://railstutorial.jp/ の内容をなぞる感じであれば、何らかの開発をしたことがある前提だと、2～3日で最低限の内容は身に着きそうですね。

# おしまい
たのしめ！