LoginSignup
11
12

More than 5 years have passed since last update.

Railsで分かち書きしてみる

Last updated at Posted at 2016-01-16

概要

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
Windows10(64bit)のRubyでMeCabをつかう(Natto)

構築

1. Modelの構築

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

1.1. rbファイルの生成

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モデルは、次の要素を持ちます。

  • mhash...文字列のハッシュキー(mojiretsuのmではなくMeCabのm)
  • seqno...ハッシュキーごとの連番
  • surface...単語そのもの
  • feature...品詞

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

1.2. migration

rakeでmigrateします。

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
これ、実行する前はdevelopment.sqlite3のサイズは0KBだったので、普通に増えているように見えます。sqliteなので、ファイルを削除して実行すると、何事も無かったかのように5KBのファイルが作られます。

1.3. ちゃんと使えることの確認

ActiveRecord::DangerousAttributeErrorやその他のエラーが出ないことを確認します。

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も、次のとおり問題ないようです。

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を作成します。

  • mhash...文字列のハッシュキー(mojiretsuのmではなくMeCabのm)
  • mdigest...入力文字列(ハッシュではない!)
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ファイルの生成

コマンド
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ファイルの作成

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 %>
app\views\raw_strs\create.html.erb

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

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

<%= yield %>

</body>
</html>

2.3. routingの編集

config\routes.rb
Rails.application.routes.draw do
  resources :raw_strs
  root 'raw_strs#index'
end

2.4. controllerの編集

app\controllers\raw_strs_controller.rb(暫定その1)
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そのものの入れ方は、環境構築に関するリンク先を参照してください。

Gemfile
...
gem 'natto'
...

3.2. controllerの編集

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

app\controllers\raw_strs_controller.rb(暫定その2)
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
SQLiteががんばっている様子
mecab2.PNG
一応、先頭から順にseqnoが振られて、データが登録されていることがわかります。
1レコードにつき30msかかると、長い日本語を登録すると秒単位で時間がかかる、ということですね。ひえー。。
毎回トランザクションを開いて閉じているので、これをどうにかすれば良さそうです。
→コメントを頂いたので、4.1.に組み込みました。ありがとうございます。

4. よりよいViewをつくる

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

4.1. controllerの編集

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ファイルの作成

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
mecab4.PNG

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

感想

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

おしまい

たのしめ!

11
12
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
12