概要
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ファイルの生成
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します。
C:\temp\sample>bundle exec rake db:migrate
== 20160116072657 CreateMeCabedStrs: migrating ================================
-- create_table(:me_cabed_strs)
-> 0.0017s
== 20160116072657 CreateMeCabedStrs: migrated (0.0031s) =======================
これ、実行する前はdevelopment.sqlite3のサイズは0KBだったので、普通に増えているように見えます。sqliteなので、ファイルを削除して実行すると、何事も無かったかのように5KBのファイルが作られます。
1.3. ちゃんと使えることの確認
ActiveRecord::DangerousAttributeErrorやその他のエラーが出ないことを確認します。
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も、次のとおり問題ないようです。
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...入力文字列(ハッシュではない!)
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ファイルの作成
<%= 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 %>
空のファイルを作らないと動かなかったので、とりあえず空のファイルを作りました。
なお、application.html.erbは次のようにしておきます。(特に修正しなくてよいと思いますが)
<!DOCTYPE html>
<html>
<head>
<title>Sample</title>
</head>
<body>
<%= yield %>
</body>
</html>
2.3. routingの編集
Rails.application.routes.draw do
resources :raw_strs
root 'raw_strs#index'
end
2.4. controllerの編集
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そのものの入れ方は、環境構築に関するリンク先を参照してください。
...
gem 'natto'
...
3.2. controllerの編集
2.4.で作ったファイルの中身をさっそく書き換えます。
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]は品詞です。
ここまでの内容で試すと、こんな感じになります。
見た目
SQLiteががんばっている様子
一応、先頭から順にseqnoが振られて、データが登録されていることがわかります。
1レコードにつき30msかかると、長い日本語を登録すると秒単位で時間がかかる、ということですね。ひえー。。
毎回トランザクションを開いて閉じているので、これをどうにかすれば良さそうです。
→コメントを頂いたので、4.1.に組み込みました。ありがとうございます。
4. よりよいViewをつくる
createした後にredirectさせるようにして、redirectした先でデータを拾わせるようにします。具体的には、controllerでshowにredirect+redirectの処理を追加して、showに対応するviewを作ります。
4.1. controllerの編集
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ファイルの作成
<span style="font-weight:bold;"><%= @raw_str.mdigest %></span><br />
を分解してしまいました。<br />
<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
にアクセスしてみると…
分かち書きをして、かつデータが保存されるようになりました。
感想
- railsは「コマンドラインをたたきながらつくる」という感じがしました。コンパイルとか実行以外で。
- SQLiteのトランザクションを毎回開くのはかなり遅いので、どうにかしたいですね。(参考:http://uniunix.com/blog/?p=154)
- RSpecの部分をかなり無視したので、これをそのうちやろうかなー。
- http://railstutorial.jp/ の内容をなぞる感じであれば、何らかの開発をしたことがある前提だと、2~3日で最低限の内容は身に着きそうですね。
おしまい
たのしめ!