日本語の読みがなを返す Gem を作った
日本語の読みがなを返す Gem である rubyfuri
(ルビふり) を作った。
Yahoo!デベロッパーネットワーク の テキスト解析:ルビ振り を利用している。
使い方
YOUR APP ID
はデベロッパーネットワークで取得できるアプリケーション ID.
rubyfuri = Rubyfuri::Client.new('YOUR APP ID') rubyfuri.furu('大きな空') #=> 'おおきなそら' rubyfuri.furu(['広い', '日光', '時計']) #=> ['ひろい', 'にっこう', 'とけい']
作った理由
はてなインターンの前半課題に id:SWIMATH2 さんが ルビふりAPI を利用していたのを思い出して、自分も使ってみた。
この API, ただ読みがなを返してくれるわけじゃなくて以下のように形態素っぽい形にして返してくる(形態素ではない)。
<resultset xmlns="urn:yahoo:jp:jlp:FuriganaService" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemalocation="urn:yahoo:jp:jlp:FuriganaService http://jlp.yahooapis.jp/FuriganaService/V1/furigana.xsd"> <result> <wordlist> <word> <surface> 青い </surface> <furigana> あおい </furigana> <roman> aoi </roman> <subwordlist> <subword> <surface> 青 </surface> <furigana> あお </furigana> <roman> ao </roman> </subword> <subword> <surface> い </surface> <furigana> い </furigana> <roman> i </roman> </subword> </subwordlist> </word> <word> <surface> 空 </surface> <furigana> そら </furigana> <roman> sora </roman> </word> </wordlist> </result> </resultset>
で、パースするのに一手間かかって面倒だったので Gem を作った。
便利なところ
rubyfuri
には、実際に API とやり取りするより簡略化されているのともうひとつメリットがある。配列を与えた時、各要素をまとめて一回のリクエストで処理しているため、API 使用回数の節約、また愚直にひとつの文字列につき 1 リクエストを直列に送るよりも高速化される。
学び
ちゃんとテストを書こう、と思って作り始めた Gem なんだけど、このエントリを書いているときにテストあんまり書けてない。
それでも RSpec でいくつかテストを書いての学び。describe
節で対象メソッドを記述して、その下に subject
でテストする処理を書く。で、その下に context
節を作ると DRY な感じで良く書ける。
describe '#furu' do subject do instance.furu(text) end context '文字列が空の場合' do let(:text) do '' end it '~~~~' do # ~~~~ end context '配列が与えられた場合' do let(:text) do ['今日は', '雲が', '青い'] end it '=====' do # ============ end end end end
まとめ
API ラッパー作った。
Rack のソースコード読んでる
途中だけどあとちょっと書ききるのがめんどくさくなったので公開する。書き始めたのは今週月曜日なのにこの進捗っぷりは、バイトだったりやる気が出なかったりで。
「他人のソースコードを読むのは良い学習だ」っていうのは明らかで、行うべきだと思う。
でこのエントリを書きはじめるつい数分前まで id:shgam さんと リモートソースコード読書会的な感じで Rack のソースコードを読んでた。読書会っていってもたいそうなものじゃなくて、ハングアウトで通話しながら Cloud9 を介して、2人で一行一行を追いながら読んでいく感じ。Rack けっこう大きいし、低いレイヤーの直接触ったことがないライブラリだったので、読むのは苦労してる。「うーん……うーん……」と悩んで、コメントにある URL を開いたら CGI の仕様書のような内容でそれもちょっと読んでみたり……。
余談だけど Cloud9 すごかった。ついさっき教えられて 1~2 時間触っただけだけどなんかすごかった。リポジトリを fork してきてそれをブラウザ上でけっこうリッチな感じのエディタで開けてそれを共有するとカーソルやら選択範囲がシェアされて……、みたいな。
なぜ Rack か
Rack は Ruby を使ってる Web 開発者ならだれでもほぼ絶対お世話になっているであろうライブラリ。webrick なんかのサーバーアプリケーションと Ruby のコードをつないでくれるやつ。Ruby on Rails や Sinatra をはじめとした Ruby の web アプリケーションフレームワークは、だいたい Rack を基にして作られている(よね?)。
一番好きな言語は Ruby で Web 開発者なんだからそりゃ超基礎となる Rack の内部実装くらい知っておかないといけないだろう、ということで読み始めた。
というのはこの瞬間に考えた理由で、本当は id:shgam さんが読んでたから。idobata で read ルームを作ってそこにコードリーディングの進捗を流されていたのを見ていて、それに参加させてもらった形。
コードリーディング
Rack は 2015年4月21日 master ブランチ最新版のもの。
どこから読むか
だいたい実行する末端から読んでいってる。 rack は実行方法が 2 つあって、コード中で require してメソッドを呼ぶか、コマンドラインから ru ファイルを呼び出して実行するか。ru は rackup の略だと思う。名前だいじ。
今回はコマンドラインから実行する方で追っていく。
コードの中で実行
require 'rack' app = Proc.new do |env| ['200', {'Content-Type' => 'text/html'}, ['A barebones rack app.']] end Rack::Handler::WEBrick.run app
コマンドラインから
config.ru
run Proc.new { |env| ['200', {'Content-Type' => 'text/html'}, ['get rack\'d']] }
$ rackup config.ru
bin/rackup
Gem の bin ディレクトリに入ってるものは、マシンに実行ファイルとしてコピーされる。今回でいうと rackup コマンドで実行するので、bin/rackup を読む。
#!/usr/bin/env ruby require "rack" Rack::Server.start
require "rack"
で lib/rack を読み込んでる。で、Rack::Server.start
を実行してる。Rack::Server は lib/rack/server.rb に記述してある。
lib/rack/server.rb
146行目から
def self.start(options = nil) new(options).start end
Rack::Server インスタンスを生成して、それの start
メソッドを呼んでる。インスタンスが生成されているので Rack::Server.initialize
を見に行こう。
184 行目から
def initialize(options = nil) @ignore_options = [] if options @use_default_options = false @options = options @app = options[:app] if options[:app] else argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV @use_default_options = true @options = parse_options(argv) end end
ここ悩んだ。 options が引数として定義されてるんだけど、Rack::Server のインスタンスメソッドとして options
メソッドが定義してあって、「あれ?この場合って options は 引数の内容なの? それとも options メソッドを見に行くの? どっち?」ってなった。以下のコードを実行すると false が出力されたので、この場合の options は引数のもので内容はデフォルトの nil だ、ということで進めた。
class Dev def initialize(options = nil) if options p true else p false end end def options true end end Dev.new # => false
else のブロックに入ってさっそくこのコードで詰まった。
argv = defined?(SPEC_ARGV) ? SPEC_ARGV : ARGV
まず defined?
を知らなかったので調べた。こういうもの。式が定義されていれば式の種類の文字列を、定義されていなければ false を返す。? で終わるメソッドなんだから Boolean で返せよ、って思った。
a = true b = nil defined? a #=> "local-variable" defined? b #=> "local-variable" defined? c #=> nil
defined? が分かったはいいが今度は SPEC_ARGV
がわからなかった。リポジトリ全体で検索してみると、SPEC_ARGV はテストファイルでのみ使われていてテスト用に定義してある ARGV だということがわかった。
三項演算子なので SPEC_ARGV が定義されていれば argv = SPEC_ARGV
、定義されていなければ argv = ARGV
ということになる。テスト環境以外ではコマンドライン引数の配列 ARGV が代入されるようだ。
で、@use_default_options
に true
が入る。
次は @options
に parse_options(argv)
の戻り値を代入する。argv の内容は先ほどでも述べたとおりコマンドライン実行だと ARGV だ。では parse_options()
のコードを見ていこう。
Whenever × rbenv
Whenever、そのままだと rbenv でインストールした Ruby で実行してくれなくて困る。
解決するには rbenv を初期化してやる必要がある。
config/schedule.rb
job_type :runner, "export PATH=\"$HOME/.rbenv/bin:$PATH\"; eval \"$(rbenv init -)\"; cd :path && RAILS_ENV=:environment bundle exec rails runner :task :output"
実行する job_type
によって :runner
のところを、 :rake
や :command
に変更する。
参考
Railsのバッチ処理のcrontab設定をwhenever&capistranoで自動化 | DjangoAppLab