RubyでYARD定義を使って実行時にメソッド引数と戻り値の型チェックを試みる

(Ruby のカンファレンスの度に、毎度話題になる?) Ruby に型が欲しい件ですが、個人的な見解を書いておこうと思います。ちなみに私は RubyKaigi 2018 に参加しておりません。Twitterのタイムラインでの賑わいを見ていただけです。 最近、Swift を触っててそのコンパイルの重さにうんざりしているので、 型推論 は現代のマシンスペックでは基本的に辛いと思っています(カフェでノマドコーディングしたいので)。またメタプログラミングし放題の Ruby に導入するのは困難という認識です。 では、どういうときに 型定義 が欲しくなるのか考えてみます。 コードを書いて実行して確認というトライアンドエラーを減らしたい、実行前にエラーをなるべく洗い出したい。 型定義起因による補完を効かせながらコーディングしたい。これはエディタやIDEのサポートも必要です。 1 について、Rubyの場合テストでカバレッジを稼いで、なるべくエラーの芽を潰しておこうとします。しかしユニットテストにおいて end-to-end テストが存在しない場合、単純なユーティリティ関数でなければ、モックやスタブに頼るようになりチェックが甘くなります。そのためテストは通過するものの、実際通しで動かしたとき想定とは異なる型のデータ受渡しが発生してしまうことがあります。 2 について、メソッド名や引数名から型をコードの読解で推測することは可能ですが、それなりの規模のアプリケーションやライブラリではコードコメントでドキュメント定義していないと(昔の自分や)他人の書いたコードを扱うのが困難になることが多いと思います。 そして例えば YARD で引数や戻り値の型をコメントに定義して、ドキュメントを生成したりIDEでコード補完に用いることが多いでしょう。 さてここから本題ですが、1 で型チェックしないで検証から漏れてしまう問題は、テストでメソッド呼び出しの引数と戻り値をよりチェックするアサーションを明示的に書いていく必要があります。一方 2 でYARDによるドキュメントの型定義は必ずしも実際のソースコードで走る処理と一致してるか保証されない問題もあります。 そこで『YARD定義によるメソッド引数と戻り値の型チェック』を実行時に行ってみたらどうかと考えてみました。 テストで型チェックのアサーションを減らせる。 YARD定義とソースコードが一致してるか検証できる。 Rubyのソースコードに余計な定義を挿し込むことはない。 YARDはテンプレートで書きだす直前の解析データを、YARD::Registry から参照する機能を提供しています。またあらゆるメソッド呼び出し(:call)と戻り(:return)は、TracePoint 機構を使ってフックすることができます。これを組み合わせれば、割と簡単に、実行時に定義どおりに適切な型の引数が指定されてメソッド呼び出され、適切な戻り値が返っているかをチェックすることできると思います。 ということで、試してみましょう。 lib/dog.rb これが YARD 定義を書いた検証対象のクラスになります。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 module Animal # # This class is Dog # class Dog # @param name [String] a name # @param weight [Numeric] weight def initialize(name, weight) @name = name @weight = weight @children = [] end # Add a child dog # # @param dog [Animal::Dog] a child dog def add_child(dog) @children.push(dog) end # Run. # # @param distance [Integer] # @return [String] message def run(distance) "#{@name} runs #{distance}." end # dummy method returns wrong type value # # @return [Integer] def dummy "a string" end end end definition.rb YARD::Registry から ClassObject と属する MethodObject を解析してチェックしやすい定義クラス MethodDefinition の集合 DefinitionStore に変換します。 ...

2018年6月3日 · Toshimitsu Takahashi

構造化ログのススメとRuby向けロガーOugaiを作った理由

構造化ログ 構造化ログ とは、機械的に処理しやすいログのことであり、その機構(ロギング)である。 英語圏では、 Structured Logging と表記される。たとえば Google Cloud の Stackdriver のドキュメントには下記の説明ページがあります。(残念ながら執筆時点で、これの日本語ページがまだできてないので、Google がどう訳すか興味深い) Structured Logging | Stackdriver Logging | Google Cloud 普通のログと構造化ログの比較 普通のログは、基本的に タイムスタンプ 、レベル 、そして メッセージ の文字列だけである。ログとして残す事象(イベント)のコンテキストになる情報はメッセージに適当に埋め込む。コンソール等で人が読みやすいものである。 構造化ログは、メッセージに埋め込んでいたコンテキストになる情報をそれぞれログ構造のフィールドに独立して持たせる。そのため後から解析がしやすい。そして出力するログはテキストベースで JSON にすることが多い。 では、見比べてみましょう。通常のRubyのLoggerと自作のOugaiでのログは次のようになります。『ユーザが記事を作成した』というログです(冠詞、削ってます)。 logger.info "User created article (user_id=#{user.id} article_id=#{article.id}" I, [2018-05-13T17:51:08.772542 #6253] INFO -- : User created article (user_id=123 article_id=45) logger.info "User created article", user_id: user.id, article_id: article.id 1 {"pid":6253,"level":20,"time":"2018-05-13T17:52:25.147+09:00","msg":"User created article","user_id":123,"article_id":45} ※デフォルトフォーマットとは異なります 見てわかる通り、普通のログは埋め込んで付帯情報を文字列化しつつ書かなくてはなりません。一方、構造化ログの方が JSON にした影響で長くなるのでコンソールでは読みづらいです。しかし読みづらいことはフォーマッタの動作環境での切替やparse機構を持つログビューワを使えば問題になりません。 構造化ログの方が解析しやすいというのは、例えば普通のログでは「あるユーザのログだけ抽出したいとき」に単に grep "user_id=10" とすると user_id が 101 など他のものまで引っかけてしまいます。構造化ログでは(主に JSONPath を使って)フィルタが $.user_id = 10 のように簡単に確実に絞り込めます。 ...

2018年5月13日 · Toshimitsu Takahashi

Ruby で Google Analytics から直近1時間のページビューランキングを取得する方法

サイトのアクセスランキングを作る方法はいろいろありますが、自前で HTTPサーバのログから計算するのは大変です。Google Analytics の API を使うとアクセス数が Ruby には Google Analytics API を操作できる Garb という便利なラッパーライブラリがあります。今回はこれを使います。 Garbのインストール gemで入れます。プロジェクトは GitHub で管理されています。 https://github.com/vigetlabs/garb # gem install garb Fetching: crack-0.3.1.gem (100%) Fetching: i18n-0.6.1.gem (100%) Fetching: activesupport-3.2.8.gem (100%) Fetching: garb-0.9.1.gem (100%) Successfully installed crack-0.3.1 Successfully installed i18n-0.6.1 Successfully installed activesupport-3.2.8 Successfully installed garb-0.9.1 4 gems installed スクリプト本体 それほど長くないので、まずソースコードを貼っておきます。 #!/usr/bin/ruby1.9.1 # coding: utf-8 require ‘rubygems’ require ‘garb’ require ‘uri’ Garb::Session.login(’<アカウント>’, ‘<パスワード>’) profile = Garb::Management::Profile.all.detect { |p| p.web_property_id = ‘<サイトプロパティID>’ p.id = ‘<アカウントID>’ } class PageTitle extend Garb::Model metrics :pageviews dimensions :hour, :page_path, :page_title end today = Time.now - 3600; start_date = today end_date = today hour = today.hour if hour < 10 hour = “0” + hour.to_s else hour = hour.to_s end cond = { :limit => 20, :sort => :pageviews.desc, :start_date => start_date, :end_date => end_date, :filters => { :hour.eql => hour } } rs = PageTitle.results(profile, cond) rs.each do |r| puts r.page_path # ページパス puts r.page_title # ページタイトル puts r.pageviews # ページビュー end ...

2012年9月5日 · Toshimitsu Takahashi

Ubuntu の Redmine を 1.3.0 にアップグレードした

Redmine 1.2.0 から 1.3.0 アップデートするのに色々とハマったのでメモしておく。 Redmine 1.3.x で必要となる Rails などの依存バージョンは以下の通りです。 Ruby 1.8.7 Rails 2.3.14 Rack 1.1.1 参照)Redmineのインストール — Redmine Guide 日本語訳 Ruby と Rails バージョン $ /usr/bin/ruby -v ruby 1.8.7 (2011-06-30 patchlevel 352) \[i686-linux\] $ /usr/bin/rails -v Rails 2.3.14 以上は、Debian Package(apt-get)で入れたもの。 Rubygems をダウンクレード Rubygems のバージョンが 1.8.13 だったが、Rails が対応していないため Rails 2.3.14が対応している 1.5系の 1.5.3 に替える。 $ sudo gem install rubygems-update -v 1.5.3 Fetching: rubygems-update-1.5.3.gem (100%) Successfully installed rubygems-update-1.5.3 1 gem installed Installing ri documentation for rubygems-update-1.5.3... Installing RDoc documentation for rubygems-update-1.5.3... $ sudo gem uninstall rubygems-update Select gem to uninstall: 1\. rubygems-update-1.5.3 2\. rubygems-update-1.8.13 3\. All versions \> 2 Successfully uninstalled rubygems-update-1.8.13 $ sudo update_rubygems RubyGems 1.5.3 installed &#65279;=== 1.5.3 / 2011-02-26 NOTE: RubyGems 1.5.0 and 1.5.1 have a broken <tt>gem update --system</tt>. To upgrade you'll need to use the manual upgrade recipe. Using sudo/su as appropriate: $ gem install rubygems-update $ update_rubygems Bug Fixes: \* Fix for a bug in Syck which causes install failures for gems packaged with Psych. Bug #28965 by Aaron Patterson. \-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\- RubyGems installed the following executables: /usr/bin/gem1.8 Rack, Rake, RDoc をインストール $ sudo gem install rack -v=1.1.2 $ sudo gem install rake -v=0.8.7 $ sudo gem install rdoc -v=2.4.3 新 Redmine の展開 RubyForge: Redmine: Project Filelist から 1.3.0 の tar.gz をダウンロードして展開。 ...

2011年12月29日 · Toshimitsu Takahashi

AKB48 の Google+ アクティビティデータを MongoDB で MapReduce してみた

MapReduce について実際やってみたことがなかったので、MongoDB で試しそうと思っていました。 そんななか、AKB48 の(18歳以上?の)メンバーが Google+ を開始しました。これで「バルス」以上に定時でかつてない負荷が Google+ にかかり始めたと思われます。ということで、扱うにはもってこいなデータなのでこれを使うことにしました。 Google+ API は今のところデータのGETしかできないようですが、それで充分です。 とりあえずメンバーの Googel+ のID(?)と名前をMongoDB のコレクションに突っ込みます。 それを元に定期的に各メンバーのアクティビティ(活動のエントリ)を取得しては、 JSON をそのままほぼそのまま別のコレクションに突っ込みました。 メンバーの取得とセット AKB48 Now on Google+ のHTMLを適当にパースして突っ込みました。 MongoDB シェルで見ると以下のように入ってます。 $ /proj/arble/mongodb/bin/mongo MongoDB shell version: 2.0.0 connecting to: test \> db.idols.find({}, { "_id":0, "id":1, "name":1 }) { "id" : "108406705498777962659", "name" : "板野友美" } { "id" : "112077362806147944184", "name" : "梅田彩佳" } { "id" : "105229500895781124316", "name" : "大島優子" } { "id" : "108367535733172853340", "name" : "大家志津香" } { "id" : "116324240483798147615", "name" : "大矢真那" } { "id" : "107135851528812577523", "name" : "小木曽汐莉" } { "id" : "111145641865855965824", "name" : "小野晴香" } { "id" : "110230842586402039931", "name" : "河西智美" } { "id" : "109547251260290757268", "name" : "柏木由紀" } { "id" : "108485060451296256117", "name" : "片山陽加" } has more Google+ からデータの取り込み Activities: list - Google+ Platform — Google Developers を使うと、ユーザのアクティビティがリストで取得できます。userId に上記の idを、collection に “public” と指定します。 ...

2011年12月11日 · Toshimitsu Takahashi

Ruby で Windows Azure Storage テーブルを操作するには

Github にある johnnyhalife/waz-storage · GitHub ライブラリを使うと簡単に操作できます。Windows Azure Storage REST API のラッパーになってます。 Rubygems でインストール $ sudo gem install waz-storage Fetching: mime-types-1.17.2.gem (100%) Fetching: rest-client-1.6.7.gem (100%) Fetching: ruby-hmac-0.4.0.gem (100%) Fetching: waz-storage-1.1.1.gem (100%) Successfully installed mime-types-1.17.2 Successfully installed rest-client-1.6.7 Successfully installed ruby-hmac-0.4.0 Successfully installed waz-storage-1.1.1 4 gems installed Installing ri documentation for mime-types-1.17.2... Installing ri documentation for rest-client-1.6.7... Installing ri documentation for ruby-hmac-0.4.0... Installing ri documentation for waz-storage-1.1.1... Installing RDoc documentation for mime-types-1.17.2... Installing RDoc documentation for rest-client-1.6.7... Installing RDoc documentation for ruby-hmac-0.4.0... Installing RDoc documentation for waz-storage-1.1.1... 認証 waz-storage ライブラリの認証方法は Base のクラスメソッド establish_connection! にアカウント名、アクセスキー、SSLを使うかどうかをハッシュで渡します。 ...

2011年12月10日 · Toshimitsu Takahashi

Rubygems で x64 環境の標準 MySQL に対して mysql ライブラリをインストールする

x64 環境だとオプションを指定しないと、mysqlclient が見つからず config で失敗する。 # gem install mysql – –with-mysql-config=/usr/lib64/mysql/mysql_config Building native extensions. This could take a while… Successfully installed mysql-2.7

2009年5月17日 · Toshimitsu Takahashi

手軽に QR コードの画像ファイルを生成できる Ruby ライブラリ rqr を試す

「ruby qrcode」でググると Y.Swetake さんの QRコードクラスライブラリ for Ruby - QRcode encode class library for Ruby が真っ先にヒットする。だが、単に php 版から移植したと説明されているのと beta 版ということもあって躊躇っていた。 RubyForge にないのかと探していたところ rqr - http://rqr.rubyforge.org/ が見つかった。 バイナリ出力をするのではなくファイルに書き出すだけの簡単な仕様になっている。また、libjpeg, libpng, libtiff を使用しているので予めインストールしておく必要がある。 yum で依存ライブラリをインストール # yum install libjpeg.i386 # yum install libpng.i386 # yum install libtiff.i386 rubygems でインストール # gem install rqr CGI を作ってみる パラメータのテキストを QRコード で返す CGI を作成してみる。 #!/usr/bin/env ruby require “rubygems” require “rqr” require “cgi” # QRcode を PNG 形式のバイナリで返す def get_qrcode_bytes(text) filepath = “/tmp/qrcode” + $$.to_s + “.png” RQR::QRCode.create do |qr| qr.save(text, filepath) end bytes = File.read(filepath) File.delete(filepath) return bytes end cgi = CGI.new text = cgi[“text”] cgi.out(“image/png”) { get_qrcode_bytes(text) } ...

2008年12月16日 · Toshimitsu Takahashi

Ruby で PHP の base_convert() のような基数変換を簡単に実装してみる

基数変換は、ハッシュやキーなどの処理で必要になることがある。php では base_convert 関数があるので簡単だ。 Ruby にはないのかと思っていたら String, Integer クラスの to_i, to_s にはそれぞれ基数を引数に渡せることがわかった。よって、この二つを組み合わせれば簡単に実装可能だ。 base_convert.rb 2 から 36 進数まで対応できる。 def base_convert(strnum, from, to) return strnum.to_i(from).to_s(to) end irb で試す irb(main):001:0> require “base_convert” => true irb(main):002:0> base_convert(“1111”, 2, 16) => “f” irb(main):003:0> base_convert(“11111111”, 2, 16) => “ff” irb(main):004:0> base_convert(“FF”, 16, 2) => “11111111” irb(main):005:0> base_convert(“f”, 16, 2) => “1111” irb(main):006:0> base_convert(“ffff”, 16, 36) => “1ekf” irb(main):007:0> base_convert(“1ekf”, 36, 10) => “65535”

2008年12月15日 · Toshimitsu Takahashi

lighttpd で Ruby の PStore によるセッション情報の書き込みがリダイレクトで中断されるみたい

require “cgi” require “cgi/session” require ‘cgi/session/pstore’ cgi = CGI.new user_id = cgi[’loginid’] session = CGI::Session.new(cgi, { ‘database_manager’ => CGI::Session::PStore, }) session[‘userid’] = user_id session.close headers.store(“status”, “REDIRECT”) headers.store(“Location”, $rooturi + “portal.cgi”) puts cgi.header(headers) exit 0 上記のような CGI を lighttpd で実行するとセッションのファイルは生成されるが、値が書き込まれずに終わる。コマンドラインで実行したり、Apache で実行しても問題はない。FastCGI対応してみたがそれでも変わらなかった。ちなみに database_manager が FileStore では問題は発生しない。 こんなコードを書いたのは、ログイン認証後にセッションへIDを保存してリダイレクトという流れの実現をしたかったのだが。。。いまいち原因が掴めない。

2008年12月7日 · Toshimitsu Takahashi