2014年5月30日金曜日

次々に出る新技術とどう向き合うか

この記事『「新技術の習得」は麻薬だ 』を読んだんですが、共感するところが多いですね。次々と有象無象の新しい技術が出てきて、まるで「よどみにうかぶうたかた」のようにボコボコと出現しては消えていくわけですが、追いかけまくっていればそれだけで気持よくなってしまうかもしれません。

実際、技術者同士で話をしていると、知識が膨大にあるというだけで表面上は優秀に見えたりするし、相手の知らないことを自分が知っているという状況の優越感は理解はできます。でもそれが実際の仕事につながるかと問われれば、かなり怪しいですね。

僕の考えとしては、「自分が実際に今使っている、あるいはこれから仕事で使う技術」について徹底的に掘り下げて追求し、その過程で関連する新技術があれば吸収していくべきだと思っています。

注意すべきは、新技術の吸収よりも、現在使っている技術の掘り下げのほうが先だということです。実は、実際に仕事で使っているにもかかわらず、深く知っている、あるいは深く知ろうとする人って意外にも少ないです。

もちろん「深く」ってのもやりだしたらキリがないし、無意味な領域まで言ってしまっては仕方ないです。例えばWebアプリケーションの開発が仕事なのに、CPUのアーキテクチャに詳しくなっても、ちょっとそれは深く掘りすぎて反対側に突き抜けてしまった感があります。

でも、例えば自分が使っているフレームワークのソースコードを読んでみるとか、自分が使っている言語の解説本に徹底的に目を通してみるとか、そういう類のことならば少なくとも新技術を追いまくるよりは余程確実に役に立つし、実力の向上につながると思うんですよね。

そうやって、今使っている技術を掘り下げる中で関連する新技術に遭遇したならば、そこで初めて新技術を吸収すればいいと思います。そうすれば、実際の仕事に直結する形で新技術を勉強することになり、より有意義な勉強になるはずです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

capybara-webkitからpoltergeist(phantomjs)に乗り換え

RubyによるWebアプリケーションの自動化テストに便利なCapybara。JavascriptによるDOM操作も、capybara-webkitドライバを経由してテストすることができます。当初はそれで良かったんですが、とある事情によりXサーバ関連ファイル群を入れないで動かしたい場合があったので、Xサーバ関連ファイル群を必要とする仮想フレームバッファ(xvfb)への依存を避けたくなりました。

Capybaraにはもう1つ、JavascriptによるDOM操作を含むテストを実行できる手段がありまして、それがPoltergeistドライバを経由してPhantomJSを動かす、というものです。PhantomJSは仮想ブラウザみたいなもので(乱暴な説明)、詳しい説明は他サイトに譲ります。CabybaraからPhantomJSを動かすためのドライバがPoltergeistです。

ということで、Capybara-webkitからPoltergeist(経由のPhantomJS)に乗り換えることにしました。

まず、PhantomJSをインストールします。僕の環境としてはDebian7ですが、公式サイトのビルド済みパッケージを取ってきたらそのまま動公式サイトにLinux用のビルド済みバイナリがあるので、入手してください。2014/05/29現在の最新版はver1.9.7であり、
"phantomjs-1.9.7-linux-x86_64.tar.bz2"というリンクからダウンロードできます。

解凍して出来たディレクトリの bin/phantomjs をPATHの通った適当な場所に移動します。

例)$HOME/bin に移動する場合

[user@host]% tar jxvf phantomjs-1.9.7-linux-x86_64.tar.bz2
[user@host]% cp phantomjs-1.9.7-linux-x86_64/bin/phantomjs ~/bin

なお、phantomjsの動作には fontconfig, freetype ライブラリが必要ですが、最近のLinuxデスクトップ環境ならば、ほとんどの場合にはすでに入っているでしょう。

ruby側の対応は巷のサイトに書いてあるとおりでOKです。まずGemfileへPolgergeistの記述を追加します。

gem 'poltergeist'

僕はCucumber経由でcapybaraを使ってますので、features/support/env.rb に必要ライブラリの require と default_driver, javascript_driverの変更を記述します。(Javascirpt関連テストの時だけ使いたい場合は、default_driver側の記述は不要です。)

require 'capybara/poltergeist'
Capybara.default_driver = :poltergeist
Capybara.javascript_driver = :poltergeist

なお、Cucumberのテスト自体は書き換えずに動作しました。まだそんなにテストの量がないせいか、速度の違いは感じられません。

PhantomJSは活発に開発されているようなので、今後も色々と改善が期待できますね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月29日木曜日

Padrinoの起動時のロード順・フック実行について

今回のテーマは実は、Padrinoにおいてconfig/boot.rbにはbefore_loadフックとafter_loadフックの登録を行うコード記述があるけれど、config/boot.rbファイルのトップレベルに直接書くのと before_loadやafter_loadの中に書くのではどう違うのか、という疑問が切っ掛けでした。

Padrinoが生成するconfig/boot.rbは末尾で Padrino.load! を呼び出していますが、実はその中でbefore_loadとafter_loadを呼んでいます。Padino.load!の定義は、ソースコードを調べると次のようになってます。

def load!
  return false if loaded?
  began_at = Time.now
  @_called_from = first_caller
  set_encoding
  set_load_paths(*load_paths)
  Logger.setup!
  require_dependencies("#{root}/config/database.rb")
  Reloader.lock!
  before_load.each(&:call)
  require_dependencies(*dependency_paths)
  after_load.each(&:call)
  logger.devel "Loaded Padrino in #{Time.now - began_at} seconds"
  Thread.current[:padrino_loaded] = true
end

つまりbefore_loadとafter_loadの違いは、Padrinoの各種依存ファイルのrequireの前か後か、という違いです。

上記Padino.load!をよく見てみると、Loggerだけ扱いが別になっています。Logger関連の設定はbefore_loadやafter_loadの中に書いてもそれだけでは有効になりません。config/boot.rbのトップレベルかつPadrino.load! の前に記述するか、手動でLogger.setup!を呼ぶ必要があります。

で、ようやく本題ですが、上記のPadrino.load!を見ながら、実際フレームワーク全体がどう読み込まれるのか調べてみたくなりました。Loggerのログレベルをdevelに設定すると、起動時にロードしたファイルをログに出してくれるようになるので、ロードの順番がわかります。まとめるとPadrinoのロードの主要手順はだいたいこんな感じになっているようです。

  • Loggerの設定
  • config/database.rbのロード
  • before_loadフックの実行
  • lib/の中にあるファイルのロード
  • models/の中にあるファイルのロード
  • (shared/lib/の中にあるファイルのロード)※デフォルトでは存在しない
  • (shared/models/の中にあるファイルのロード)※デフォルトでは存在しない
  • config/apps.rbのロード
  • app/app.rbのロード
  • アプリケーション名::Appのインスタンス生成
  • after_loadフックの実行
  • app/controllers/の中にあるファイルのロード
  • app/helpers/の中にあるファイルのロード

なるほど。ちなみに意外だったのは、controllersとhelpersのロード順が予想と逆だったこと。でもどうせメソッド定義なので特にロード順に影響はないはずです。

ロードの順番に影響を受けるようなコードの書き方をすることは少ない(というかなるべく避けるべき)と思いますが、デバッグの役に立つこともありますので一応知っておくとよいと思われます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月28日水曜日

Padrinoのhelperで特定インスタンスを使いまわす

Padrinoではコントローラやビューから呼び出すことのできるメソッドをhelperメソッドとして定義することができます。

helperメソッドはリクエスト中に何度も呼ばれたりするので、例えばサーバへの接続を行う場合、何度も接続インスタンスをnewせずに使いまわしたい場合がありますよね。

実はある特定のインスタンスを使いまわす方法は単純で、helperメソッド内でインスタンス変数に入れておけばいいです。例えば、LDAPサーバへの接続を取得するヘルパーメソッドを書くとしたら、

MyApplication::App.helpers do
  def ldap_conn
    @ldap_conn ||= LDAP::Conn.new('localhost')
  end
end

みたいな感じになりますね。見ての通り、@ldap_connが未定義の場合は新規接続を作成してそのインスタンスを@ldap_connに代入するとともに返し、すでに@ldap_connが定義されていれば@ldap_connの中身を返します。

ちなみにこのインスタンス変数@ldap_connはどのインスタンスに属するかというと、MyApplication::Appのインスタンスに属することになります。これは get や post メソッド内で定義したインスタンス変数と同じ扱いです。テンプレートからも参照できることからわかるように、これらのインスタンス変数は同じリクエスト中は直接呼んで使いまわすことが出来ます。

今回はLDAP接続を例にしましたが、DBサーバやキャッシュサーバなどの各種サーバへの接続など生成処理の重いインスタンス全般に使えると思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月27日火曜日

Redis中のRackセッションのデータを直接見る

RubyのフレームワークPadrinoで開発しているアプリケーションのセッション管理をRack::Session::Redisでやることにしたわけですけれども、検証作業中にRedisに保存したセッションのデータをRack::Session::Redisを介さずに直接見てみたくなりました。

というわけで、Redisに格納されているセッションのデータを直接見る方法です。
セッションIDが分かっていることが条件です。RackアプリケーションでのセッションID取得方法はこちらを参照。
irbからRedisクラスを直接叩いて見てみることにします(以下、見やすいように改行を加えてあります)。

[user@host]% bundle exec irb
irb(main):001:0> require 'redis'
=> true

irb(main):002:0> redis = Redis.new(:url => 'redis://127.0.0.1:6379/0')
=> #<Redis client v3.0.7 for redis://127.0.0.1:6379/0>

irb(main):003:0> Marshal.load(redis.get('rack:session:セッションID'))
=> セッションのデータ

という感じで直接見ることができます。

Rackアプリケーションのセッションデータは、デフォルト設定では rack:session という名前空間の下に収められています。この名前空間は変更可能ですがあまり変更することはないでしょう。

データはMarshal.#dumpでシリアライズされているようで、読みだす時はMarshal.#loadを使えばよいです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月24日土曜日

やっぱりセッション管理はRack::Session::Redisで

昨日Rack::Session::Dalliを使うって言った舌の根も乾かぬうちに別のことを言い始めたよコイツ、とのツッコミを受けそうです。すいません。いや実は職場の人にRack::Session::Redisってのもあるよ、とアドバイスを頂きまして、調べてみたらRedisが結構良さそうだし面白そうなのでRedis使いたくなってしまったのですよ。

ちなみに、Rack::Session::Redisについて調べてみると、このクラスを提供しているライブラリが2つ見つかります。rack-session-redisredis-rack の2つですね。

redis-rackのほうが新しいです。ソースコードも読んでみましたが、redis-rackのほうがよく整理されている印象を受けました。redisにデータを保存するための抽象化レイヤが redis-store として分離されていて、redis-rackはその上に乗っている構造のようです。

ということで、redis-rackを導入します。以下、Padrinoフレームワークを使っている前提です。

Gemfileに以下の記述を加えます。

gem ‘redis-rack’

app/app.rbに以下の記述を加えます。

set :protection, false
set :protect_from_csrf, false
disable :sessions
use Rack::Session::Redis, :expire_after => 30 * 24 * 60 * 60
use Rack::Protection
use Rack::Protection::AuthenticityToken, :authenticity_param => '_csrf_token'

セッションに使うライブラリを変更するため disable :sessions とするのですが、:protection と :protect_from_csrf は enable:sessions を前提にしておりエラーになるため事前に falseに設定し、その後手動で該当するミドルウェアである Rack::Protection と Rack::Protection::AuthenticityToken を use します。

なお、Redisの接続先はRack::Session::Redisのデフォルトのオプションで

 :redis_server => 'redis://127.0.0.1:6379/0/rack:session'

と定義されてます。特に変更の必要はないので今回はサーバを指定していません。オプションはセッションの有効期限だけを指定しています(上の例だと30日を秒数で指定)。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月23日金曜日

セッション管理は Rack::Session::Dalli で

昨日書いたように、Rack::Session::Pool使用中にセッションが破棄される という問題が発生してまして、その打開策として最初 Rack::Session::Memcache への移行を検討してました。

Rack::Session::Memcache はバックエンドに memcache-client パッケージを使うのですが、実は memcache-client のメンテを担当している開発者によって memcache-client の後継である dalli というパッケージが出ていました。

ここで、memcache-client と dalli という2つのパッケージが登場したので、他にもあるかと一応調べたら memdached という名前のパッケージもありますね(直球な名前だな)。これはCで書かれたAPIをRubyから呼ぶもので、速さだけで言ったら最速みたいですけれども、Rackに組み込む簡単な方法はパッと見つかりませんでした。やるとしたらRack::Session::Memcache を書き換えるのが一番現実的でしょうけど、あまりやりたくありません。

一方、dalli には Rack::Session::Dalli クラスが付属していて、これを使うと簡単に導入できるみたいでした。これは魅力的。さすがにdalliはPure Ruby実装なので速度面で劣るものの、それでもパフォーマンスは結構良好なようですので大丈夫でしょう。今回はそこまで速度重視の目的でもないですし。

ということで、 Rack::Session::Dalli を導入します。Padrinoフレームワークを前提に説明しますので、適宜読み替えてください。

Gemfileに

gem ‘dalli’

と加えます。そして、config/boot.rb に

require 'rack/session/dalli’

を追記します(Bundler.require では自動的にロードされないためです)。

そして、app/app.rb に

use Rack::Session::Dalli, :expire_after => nil, :memcache_server => '127.0.0.1:11211’

って感じに設定すればOKです。サーバはデフォルトで 'localhost:11211’ になってますが、IPアドレスを打たないときちんと認識しなかったので明示的に '127.0.0.1:11211’ を設定しています。

参考1→ http://stackoverflow.com/questions/12786976/how-to-specify-memcache-server-to-racksessionmemcache/12787492#12787492
参考2 →http://www.banana-systems.com/2010/09/rails3_memcached/

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月22日木曜日

Rack::Session::Pool使用中にセッションが破棄される

Rack::Session::Poolを使っていると、突然セッションIDが変わる、という問題にぶちあたりました。しかもセッションIDが変わってもセッションに保存した値は引き継がれず消えるので、どうやらセッションが破棄されているようです。

Cucumberでのテストでは引っかからないので今まで気付きませんでした。ブラウザで実際に手動で操作していて初めて気づいた問題です。普通に動作していると思ったらいきなりセッションが消えてログイン状態が無効になったりします。問題発生のタイミングや間隔もランダムで、原因は、正直よくわかりません。

ネット上を探してみると、Rack::Session::Poolを使用中に似たような症状を報告している人がいました。
http://d.hatena.ne.jp/libera-softvaro+dev/20110324/1300982695

上記URLのページではリクエストごとにCookieの値が変わると書いてあります。Rack::Session::Poolを使う場合Cookieの値はセッションIDなので、同じような症状に思えます。unicornだと発生しないと書いてありますが、僕はunicornを使っており、リクエストごとに変わるということはないものの、しかしランダムなタイミングで突然セッションIDが変わります(セッションが破棄されている)。

一応原因特定のため一時的に Rack::Session::Cookieに戻してみると、やはり症状は起きず、この問題は全く発生しなくなります。

う~ん、Rack::Session::Poolはダメなのか・・・。しかし、Rack::Session::Cookieだとセッションのデータも全部Cookieに保存するからセキュリティ的に避けたいところです。となると、やっぱ、Rack::Session::Memcacheしかない気がします。ということで、memcachedの導入を本格的に考えますかね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月21日水曜日

Rubyのコメント形式とYARD

PHPの時はPhpDoc形式のコメントをIDEが読み取って連携してくれるという事情もあり、ソースコードにはPhpDoc形式のコメントをつけるようにしてました。このPhpDocでは様々なタグを付けることができて、例えば関数の引数は@paramタグで指定できたりします。

ではRubyの場合はどうかと思って、今さらながら調べてみました。Rubyの場合、ソースコード内に記述するコメントのパーサとしてはRDocが代表的みたいですね。

調べてみると、RDocのコメントには、特に関数の引数や引数の型を指定する形式とか、そういうのはないみたいです。ちょっとイメージが湧かないので、有名なライブラリのソースコードをあたってみました。

ActiveRecordの場合、クラスやモジュールの先頭にガッツリ長文で説明が例も交えて解説してあります。

Rackの場合、ドバっと書いてあるファイルと、全然書いてないファイルの差が激しいです。まあ、必要なとこだけ絞って書いてあるんでしょう。

正直、決まりとか慣例とかあまり無いように思いました。うーん、自由に書けばいいのかなぁ。

などと思っていたら、どうやら、PhpDocに似たYARDってのがあるらしいじゃないですか。
※詳細はリンク先参照。
参考1 http://d.hatena.ne.jp/kitamomonga/20110825/ruby_yardoc_tag
参考2 http://morizyun.github.io/blog/yard-rails-ruby-gem-document-html/

そうそう、こういうのを探してたわけですよ。これならPhpDocと同じような感覚で書けますね。YARD自体の解説は上記リンク先に素晴らしい解説があるのでここではしませんが、やっぱりソースコードにコメントを入れるものとしては引数と戻り値を指定するタグくらいはあって欲しいわけで、そういう意味ではYARDはバッチリですな。

というわけで、今後YARD形式でコメントを書いていこうと思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月20日火曜日

Padrinoで全コントローラ共通の処理を書く

SinatraベースのフレームワークPadrinoはMVC構造を用意してくれるので処理を分かりやすく書くことができます。コントローラは自動的に雛形が用意されるので、各コントローラに関する処理はその雛形を編集すればよく、編集すべきファイルが分かりやすいです。

では、例えばログインを必要とするシステムにおけるページ表示前の認証の確認処理など、全コントローラに共通の処理はどこにどのように書けばよいでしょうか。

Padrinoのプロジェクトルートから見て、app/app.rbに書けばよいと思います。この中のAppクラスの宣言文の中に、before〜endを使って書けば全コントローラの処理前に実行される処理が書けます。

例えば上で挙げたログイン判断の場合ですと、

before do
  if /^\/login/ =~ request.path_info
    # ログインコントローラの場合、ログイン済みならトップ画面へリダイレクト
    redirect url_for(:top, :index) if logged_in?
  else
    # ログインコントローラ以外の場合、未ログインならログイン画面へリダイレクト
    redirect url_for(:login, :index) unless logged_in?
  end
end

のような感じで書けますね。上記のようにURLによって処理を分けて書くこともできます。また、ヘルパーメソッドも使えます。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月17日土曜日

Cucumber(+Capybara)ステップ定義の実用的サンプル

現在のプロジェクトでは初めてBDDを採用して開発してます。具体的にはCucumberを使ってステップ定義を書いています。

Cucumberについて調べると、確かにサンプルとしてGherkinによるステップ定義の例は色々出てくるんですけれども、日本語によるステップ定義で実用的というかそのまま実務に使えるサンプルってネット上に出ているものが少ない気がします。実務的じゃないものは沢山見つかるんですけどね。

ということで、BDDはまだ初心者といえる私ですけれども、実務ですぐに使える日本語で書いたGherkinによるステップ定義のサンプルを晒してみたいと思います。なお、具体的なテストコードの記述にはCapybaraを利用しています。

ちょっと長いですが、実用的なサンプルということでご理解ください。ではどうぞ。

もし(/^".*?\((.*?)\)" 画面にアクセス(?:し(、)|した)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  visit t_path
  puts '' if t_eos.blank?
end

もし(/^HTTPステータスコードが "(\d{3})" で(?:あり(、)|ある)( debug)?$/) do |t_code, t_eos, debug|
  show_debug_info if debug
  expect(page.status_code).to eq(t_code.to_i)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" 欄に "(.*?)" と入力(?:し(、)|した)( debug)?$/) do |t_locator, t_with, t_eos, debug|
  show_debug_info if debug
  page.fill_in(t_locator, :with => t_with)
  puts '' if t_eos.blank?
end

もし(/^".*?\((.*?)\)" ボタンを押下(?:し(、)|した)( debug)?$/) do |t_locator, t_eos, debug|
  show_debug_info if debug
  page.click_button(t_locator)
  puts '' if t_eos.blank?
end

ならば(/^ページタイトルが "(.*?)" で(?:あり(、)|あること)( debug)?$/) do |t_title, t_eos, debug|
  show_debug_info if debug
  expect(page.title).to eq(t_title)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:し(、)|すること)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" が存在(?:せず(、)|しないこと)(\(非同期\))?( debug)?$/) do |t_css, t_eos, t_async, debug|
  show_debug_info if debug
  expect(page).to have_no_css(t_css, :wait => t_async ? async_time : 0)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" 画面に遷移(?:し(、)|すること)( debug)?$/) do |t_path, t_eos, debug|
  show_debug_info if debug
  expect(page.current_path).to eq(t_path)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:され(、)|されていること)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).to eq(t_text)
  puts '' if t_eos.blank?
end

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:されず(、)|されていないこと)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_eos, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).not_to eq(t_text)
  puts '' if t_eos.blank?
end

def async_time
  Capybara.default_wait_time
end

def show_debug_info
  puts 'Respnse Headers: ' + page.response_headers.to_s
  puts 'Status Code: ' + page.status_code.to_s
  puts 'Current Url: ' + page.current_url
  puts "HTML:\n" + page.html
end

以上になります。いくつかポイントを解説します。

URLやボタンや入力欄などの条件はカッコ内に記述するようにしているものがあります。例えば画面へのアクセスは "画面名(URL)" という記述です。他にも "ボタン名(HTMLのID)" とか、 "探したい要素の呼び名(CSSセレクタ)" とかですね。これはテストの見た目のわかりやすさと同時に、ステップ定義を使いまわせることを実現するためです。

各ステップは末尾に " debug" (半角スペースの後ろにdebug) とつけると、そのステップ実行直前のレスポンスについて、レスポンスヘッダ、ステータスコード、URL、HTMLコードをデバッグ情報として出力します。テストをしながら失敗したステップの時の状態が知りたいことってあると思うので、こうしてステップ定義にデバッグ情報の出力オプションを付けておけば便利ですよね。

あと、末尾に "(非同期)" とつける事のできるステップもあります。これは、Capybaraの機能で、例えばレスポンス中に指定した要素が存在するか確認するコードの場合、見つからなくても非同期処理の可能性を考慮して一定時間待ってから再度探してくれる、という機能を切り替えるためのものです。Capybaraではデフォルトで毎回2秒待ってくれるのですが、非同期を想定していない場合、この待ち時間は無駄です。テスト駆動をする場合はテスト失敗を頻繁に起こすのでなおさら無駄な待ち時間は無くしたいです。ということで、末尾に "(非同期)" と付けない場合はCapybaraの待ち時間を0秒に設定することで、無駄な待ち時間が発生しないようにしています。

あと、細かい点になりますが、ステップ定義文の最後の語尾に読点(、)をつける書き方とそうでない書き方の2通りできるようにしてまして、後者(読点なし)の場合には、次の行に空行を出力するようにしています。これは、Cucumberの実行時に、ステップ定義のソースコード上での空白行を無視して出力してしまって見辛いので、強制的に空行を再現させる目的で付けています。言葉で説明するとわかりにくいと思いますが実行結果を見れば一発でわかると思います。こんな風になります。


空行が出力されるので、結果がぐっと見やすくなりますね。

もしかしたら今後開発を進めるうちにまた色々変わってきたらアップデート版を書くかもしれませんし、書かないかもしれませんが、参考になれば幸いです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月16日金曜日

やっぱり、銀の弾丸などない

僕も一時期はアジャイルに傾倒して、アジャイルこそ開発現場の諸問題を解決する究極の方法だなんて考えてた時期もありましたが、結局状況によりけりだってことを分かってきました。

テスト駆動開発(TDD)にしても、TDD原理主義者に言わせれば、いかなる状況においてもテストを先に書くべきだ、って言うんでしょうけれど、それもどうなんですかね。例えば今まで経験のない言語やフレームワークやライブラリを使う場合に、どういう結果が返ってくるか予想すらつかない場合には、テストを先に書くよりもまず、実際に色々コードを書いてみて、その動作やコーディングの勘所がある程度つかめる状態になってからテストファーストに切り替えたほうが効率がいいと思いますし。

アジャイルにしろTDDにしろ、もちろん僕は非常に優れた方法論だと思うし、実際に自分自身でも仕事の中で使っています。ですが、原理主義的にギチギチにルールを縛って適用するんではなくて、ちょっとルールを破ることがあっても、現実にうまい具合にあてはめていけるんならそれでいいんじゃないですかね。

銀の弾丸などない、という言葉をあらためて噛みしめるこの頃であります。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月15日木曜日

ruby-ldapでエントリを再帰的に削除する

※途中、試行錯誤の過程でボツにしたコードも載せてるので、結論だけ見たい方は途中の過程をすっ飛ばして下の方をご覧ください。

ruby-ldapを使ってrubyからLDAPの操作をしている時に子エントリを持つエントリを削除しようとするとエラーが発生し、"Operation not allowed on non-leaf” と言われます。

ruby-ldapの場合は特にエントリを再帰的に削除してくれるような便利なメソッドなんか用意されていません。ですので、先に全ての子エントリを削除するコードを自力で実装しないといけません。

最初に思いついたのは、エラーの発生を捕捉し、子エントリがあって処理に失敗した場合には各子エントリに対して再帰処理をかける方法です。

ということで最初に書いたコードがこちら。

# 指定DNを子も含めて再帰的に削除する
# ldap_connはLDAP接続を返すメソッドとして別途定義
def ldap_delete_r(dn)
  begin
    ldap_conn.delete(dn)
  rescue LDAP::ResultError
    if ldap_conn.err == 66 # "Operation not allowed on non-leaf"
      ldap_conn.search(dn,  LDAP::LDAP_SCOPE_ONELEVEL, '(objectclass=*)', ['dn']) do |entry|
        ldap_delete_r(entry.get_dn)
      end
    else
      raise
    end
  end
end

これで一応望み通りに動作しますが、ちょっとスマートではありません。
rubyに限らず一般的に例外処理のためのbegin〜rescue〜end節のような構文は処理が重いので、できれば例外捕捉をしなくて済む方法が望ましいです。
さらに、削除試行して失敗してから子エントリを検索していますが、そもそも子エントリを先に検索すればそ無駄に失敗する削除処理をしなくても済むはずです。

よって、削除処理の前に子エントリを検索しに行き、ヒットした各子エントリに対して再帰処理をかければよさそうです。子エントリがヒットしなければそのままエントリを削除します。

ということで修正したコードがこちら。

# 指定DNを子も含めて再帰的に削除する
# ldap_connはLDAP接続を返すメソッドとして別途定義
def ldap_delete_r(dn)
  ldap_conn.search(dn, LDAP::LDAP_SCOPE_ONELEVEL, '(objectclass=*)', ['dn']) do |entry|
    ldap_delete_r(entry.get_dn)
  end
  ldap_conn.delete(dn)
end

この方がスマートですね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月14日水曜日

RackアプリケーションでセッションIDを取得する

Webアプリの開発をしていると、現在のセッションIDを取得したくなることがあります。PHPですとsession_idというその名もズバリの関数が言語標準で用意されてたりしますが、Rubyだと言語標準にはないです。そもそもPHPと違ってセッションの仕組み自体言語標準ではなく、よく使われるのはRack::Session系のライブラリです。これは以前紹介したように幾つか種類があります。

しかしRack::Session系のライブラリで、現在のセッションID取得をどうやるかについてはドキュメントに分かりやすく書いておらず、探しにくかったです。(Rack::Session::Cookieの場合のみ、セッションのデータ自体にセッションIDが格納されるようですが、それ以外ではIDは入りません。)結局ソースコード読んで見つけました。結論から先に言いますと、

env['rack.session'].id

で取得できます。SinatraやPadrinoを使っている場合はもっと簡潔に

session.id

でOKです。(Sinatraにおけるsessionメソッドはrequest.sessionを呼びだします。Rack::Request#sessionは@env['rack.session']を返します。Sinatraにおけるenvメソッドはインスタンス変数@envへのアクセサですので、結局同じことになります。)

なお、今回(メインで)参照したソースコードはこれ↓。
https://github.com/rack/rack/blob/master/lib/rack/session/abstract/id.rb

上記URLにはRack::Session::Abstract::SessionHashとRack::Session::Abstract::IDの定義が書かれています。

ここにセッションIDを取得していそうなメソッドを発見。Rack::Session::Abstract::ID#current_session_id です。こいつを定義している部分で、

def current_session_id(env)
  env[ENV_SESSION_KEY].id
end

とやってますね。ちなみにRack::Session::Abstractモジュールの冒頭の方で

ENV_SESSION_KEY = 'rack.session’.freeze

と定義されてます。よって、env[‘rack.session’].idを呼べばセッションIDが取得できるとわかります。

なお、env[‘rack.session’]の中身は何かというと、Rack::Session::Abstract::ID#prepare_session で

env[ENV_SESSION_KEY] = session_class.new(self, env)

としてまして、ここでsession_classが何かといえば、Rack::Session::Abstract::ID#session_class の定義が

def session_class
  SessionHash
end

となってます。要するに、env[‘rack.session’]に入っているのはRack::Session::Abstract::SessionHashのインスタンスなのでした。ちなみに、アプリケーション中で p env[‘rack.session’] などとするとハッシュに見える結果が返ってきますが、これは inspect メソッドが定義されているからです。

def inspect
  if loaded?
    @data.inspect
  else
    "#<#{self.class}:0x#{self.object_id.to_s(16)} not yet loaded>"
  end
end

インスタンス変数@data はハッシュですので、 inspect した結果がハッシュになるのですね。(Kernel.#p は内部的にObject#inspect を呼びます。)

ついでに、Rack::Session::Abstract::SessionHash#id を見てみますと

def id
  return @id if @loaded or instance_variable_defined?(:@id)
  @id = @store.send(:extract_session_id, @env)
end

となってまして、セッションIDはRack::Session::Abstract::SessionHashのインスタンス変数@idに保持されていることがわかります。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

※2014/05/22更新:Rack::Session::Cookieの場合のみ、セッションのデータ自体にセッションIDが格納される点について追記。
※2014/06/04更新:SinatraやPadrinoの場合のより簡潔な書き方を追記。

2014年5月13日火曜日

PadrinoでSinatra::Flashを使う(Padrino::Flashでなく)

Webアプリを開発してますと、エラーメッセージを画面に出力するときに、次のリクエストの時に出力させたい、なんてことがよくあります。例えば何かの処理をした後に別のURLにリダイレクトさせて、その画面でエラーメッセージを出力させる場合がそうですね。

セッション変数に値を保持すれば、次回以降のリクエストに値を引き継げますが、もしこれがエラーメッセージだとすると、表示したらもう使わないので消さなければいけません。いちいち自分で消すのは面倒です。そこで、次のリクエストまでに限り値を保持するしくみがフレームワークやプラグインで用意されていたりします。このような仕組みは "Flash" と呼ばれています。(綴りは同じですがAdobeのFlashとは別物です。)

Sinatraの場合、公式ページにてRack:Flashを使うように書いてあります。しかしPadrinoでも使おうとしたらエラーになって使えませんでした(Gemfileに追加するだけでエラーになります。詳しい原因は追求してません)。

ということで、Padrinoの設定ファイルに記述があるSinatra:Flashを使うことにします。Sinatra:Flashは、セッション変数 session[:flash] にデータを保持しますが、直接セッション変数をアクセスせずに、flash[:key]を通じてアクセスします。ちなみに動作を見ていくとなかなか面白いです。まずは以下のコードと実行結果をみてください。

flash[:error] = "hoge"
p flash[:error]                   #=> nil
p flash.now[:error]               #=> nil
p flash.next[:error]              #=> "hoge"

flash.now[:error] = "fuga"
p flash[:error]                   #=> "fuga"
p flash.now[:error]               #=> "fuga"
p flash.next[:error]              #=> "hoge"

flash.next[:error] = "foo"
p flash[:error]                   #=> "fuga"
p flash.now[:error]               #=> "fuga"
p flash.next[:error]              #=> "foo"

入れたはずの値が入っていないように見えますが、実は以下のような仕組みです。flashはSinatra::Flashオブジェクトのインスタンスであり、Sinatra::Flashの[]= メソッドは@nextインスタンス変数に値を代入するようになっていて、[] メソッドは@nowインスタンス変数の値を出力します。そしてFlashオブジェクトのインスタンス変数@next, @nowには同名のメソッドでアクセスできるようになっています。

つまり代入は@nextへ、出力は@nowからってわけですね。よって一見上記は不思議に見えるようですがこれで正しい動作なのです。

でもって、アプリケーションのafterフックで現在の@nowの内容を@nextでまるごと置き換えてしまいます。これで次回リクエスト時には@nextの内容にアクセスされることになり、@nextへ記録した内容は次回リクエストまで限定で有効、ということになります。

ちなみに、Padrino:Flashというライブラリもあります。こちらはセッション変数 session[:_flash] にデータを保持します。使い方はSinatra:Flashと同じで、実際、Gemfileの記述をPadrino:Flashに変えただけでエラーなく動作しました。

ではデータの保存先以外に何が違うかというと、値を保存する際に、その値がシンボルである場合、自動的にI18n.traslateを通してくれること。そして付属するヘルパーメソッドのHTML出力形式が違うこと。

でも、今回は値の保存形式は独自に決めたかったし(エラーの種類ごとに更に内部にハッシュを持たせたかった)、その出力のためのヘルパーメソッドも自分で書くことにしたので、正直どちらでも良かったんですが、結局余計なことをしないシンプルなSinatra:Flashを採用したってわけです。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月10日土曜日

Safariで"戻る・進む"時のキャッシュを無効にする

Webアプリの開発時に、ブラウザの戻る・進むボタンを押した時にキャッシュを使われるのは時として都合が悪いことがあります。そういう時は、HTTPレスポンスヘッダを設定するのが一つの手段です。

ちなみにPHPですと、このキャッシュを無効化する機能はキャッシュリミッタとして言語に標準で実装されていたりします。さすがWebアプリに特化した言語ですよね。
http://www.php.net/manual/ja/function.session-cache-limiter.php

上記マニュアルによれば、該当するヘッダは

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

ですね。そこで、現在Rubyで開発しているシステムで同様のレスポンスヘッダを設定してみたところ、上手くいくと思ったらこれがSafariだと効きません・・・。

色々レスポンスヘッダをいじってみますが、結局ダメでした。ちなみに調べてみるとヘッダでもHTMLのメタタグでもダメで、Javascriptに頼るしかないようです。
http://stackoverflow.com/questions/11979156/mobile-safari-back-button

上記URLによると、次のようなスクリプトを使えばよいようです。

window.onpageshow = function(event) {
  if (event.persisted) {
    window.location.reload()
  }
};

URL中の説明にもありますが、jQuery経由では動作しないので生のJavascriptで書きます。これで Safari でも"戻る・進む"時にキャッシュではなくサーバから読むようになります。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月9日金曜日

Capybara で一時的に非同期処理の待ち時間を調整する

Ruby で開発している Web アプリのテストを強力に支援してくれる Capybara。デフォルトで非同期処理のテストにも対応してまして、特定の要素を見つけ出したり、条件に一致する要素の存在確認をしたりするテストにおいて、失敗した場合に一定時間(デフォルトでは2秒間)待ち、再度DOMを読み直してテストを再実行する機能が組み込まれております。

これは便利な機能ですが、しかし非同期処理を想定しないテストの時も、毎回レッドのたびに2秒待たされるのも無駄です。この非同期処理の待ち時間を変更する方法の1つは、using_wait_time メソッドを使うことです。

using_wait_time 1 do
  expect(page).to have_css '#hoge'
end

上記コードは非同期処理の待ち時間を1秒に設定してブロック内のテストを実行します。

あるいは、Capybara 2.1以降なら find メソッドにおいて :wait オプションが使えます。Capybara 2.2以降なら、matcher系メソッド(have_cssなど)でも :wait オプションが使えます。

expect(page).to have_css('#hoge', :wait => 1)

上記コードは先程載せた using_wait_time の使用例と同じく、非同期処理の待ち時間を1秒に設定してテストを実行できます。

ここで、Cucmuberとの組み合わせの例を示しておこうと思います。僕が実際使っているステップ定義の一部を紹介しましょう。

ならば(/^".*?\((.*?)\)" に "(.*?)" と表示(?:され|されていること)(\(非同期\))?( debug)?$/) do |t_css, t_text, t_async, debug|
  show_debug_info if debug
  target_node = page.find(t_css, :text => t_text, :visible => true, :wait => t_async ? async_time : 0)
  expect(target_node.text).to eq(t_text)
end

def async_time
  Capybara.default_wait_time
end

def show_debug_info
  puts page.response_headers
  puts page.status_code
  puts page.current_url
  puts page.html
end

上記のステップ定義において、末尾に (非同期) という文字列をつけると非同期処理の待ち時間をデフォルト(2秒)にし、付けない場合は0秒すなわち待たない設定としています。例えば、非同期処理を待たない場合はこんなふうになります。

ならば "ログイン名(#login-name)" に "admin" と表示されていること

ここで、もし非同期通信を待ちたければ、

ならば "ログイン名(#login-name)" に "admin" と表示されていること(非同期)

と書けばいいです。

ちなみに、このステップの実行直前にデバッグ情報を表示させたければ

ならば "ログイン名(#login-name)" に "admin" と表示されていること debug

と書けばできるようにしています。

正規表現によるステップ定義は高い柔軟性をもたせられるのが利点ですね。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月8日木曜日

Padrino で i18n (他言語化)対応

Padrinoは i18n ライブラリに対応しており、簡単な設定で使いはじめることができます。まずは、config/boot.rbにて以下の記述を追加します。

Padrino.before_load do
  I18n.enforce_available_locales = true
  I18n.default_locale = :ja
end

ちなみに I18n.enforce_available_locales を true にすると、その言語が利用可能ではない(翻訳ファイルが見つからない)時に例外を投げてくれるようになるらしいです。この設定はPadrino::Helpersモジュールによりデフォルトでtrueになっているのでわざわざ設定は不要な気もしますが、念のため明示的にtrueを指定してます(下記ソース参照)。
https://github.com/padrino/padrino-framework/blob/master/padrino-helpers/lib/padrino-helpers.rb

なお、 I18n.default_locale = :ja を  I18n.locale = :ja と書いている解説ページもちらほら見かけましたが、cucumberによるテストを実行した際に翻訳が効きませんでした。cucumberによるテストはマルチスレッドでして、どうやら、I18n.locale はスレッドローカルな設定であることが原因で  I18n.default_locale しか設定が効かないっぽいです。そこんとこの詳しいメカニズムまでは調べてませんが。まあ、I18n.default_locale のほうを設定しておけばいいと思います。

翻訳ファイルは、app/locale/ja.yml に置けばいいです。形式としてはYAMLで、

ja:
  login: ログイン
  top: トップ

みたいな感じです。

翻訳のためのメソッドは I18n.translate ですが、padrino が短縮で呼び出せるペルパーを用意してくれてます(Padrino::Helpers::TranslationHelpers)。
そのおかげで、

<%=  I18n.translate(:login) %>

と書かなくても

<%= t(:login) %>

と書くか、あるいはRails風に

<%=t :login %>

と書けば「ログイン」と出力してくれます。

ちなみに、 I18n.localize というのもあって、こちらはDate, DateTime,Timeのいずれかのオブジェクトを受け付け、日付や時刻を言語に対応した形式で表示してくれます。こちらも同様短縮形が用意されているので、

<%=l Time.now %>

と書くと

2014/05/07 21:01:00

などと表示してくれます。その他にも翻訳時のパラメータ対応など機能がありますが、また別の機会に書きたいと思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月3日土曜日

テスト駆動開発の自動テスト"以外"の部分

それにしても、テスト駆動開発を実際にやっていると、テスト駆動開発っていうのは自動化テストありきの話では全くないということに気づきます。つまり、自動化テストが全くないとしても、テスト駆動開発的な進め方・考え方というのは元からあったわけで、それが自動化テストという仕組みにぴったりマッチした、と捉えることもできるのですよ。

例えば、テスト駆動開発の基本的な流れは、
  1. テスト記述
  2. テスト実行(レッド)
  3. コード記述
  4. テスト実行(グリーン)
  5. リファクタリング
  6. テスト実行(グリーン)
  7. (1. に戻る)
と繰り返していきますよね。

でもこれ、例えばWebアプリ開発で言えば、自動化テストの代わりに、ブラウザ上で実際に手動で動かして確認することに置き換えると、実は昔からやっていたやり方になるんですよね。要するに、
  1. 実現したい動作を決める
  2. ブラウザで現在の動作を確認する(失敗)
  3. コード記述
  4. ブラウザで現在の動作を確認する(成功)
  5. リファクタリング
  6. ブラウザで現在の動作を確認する(成功)
  7. (1. に戻る)
という感じですね。リズムとしては、ブラウザによる動作確認が自動化テストに置き換わればそのままテスト駆動開発になってしまいます。ですので、テスト駆動開発を始めた時に、全く違和感なく実に自然に始めることができたんですよね。
多分この自動テスト"以外"の部分が以前からの開発の流れとぴったり重なると、テスト駆動開発を容易に採り入れることができ、恩恵を享受できると思います。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月2日金曜日

PadrinoでRack::Session::Poolを使う方法

先日、Rack::Session::PoolとRack::Session::Cookieの違いについて書きましたけれども、Padrinoで実際にRack::Session::Poolを使う設定をしてみたので、メモしておきます。

Padrinoでは、デフォルトではapp/app.rbに

enable :sessions

と記述があり、セッション管理が有効になっています。PadrinoのベースであるSinatraは上記の記述がある場合にRack::Session::Cookieをオプション無しで呼び出します。そのためここではCookieの有効期限などの細かい設定はできません。

細かい設定をしたい場合は、enable :sessions の代わりに Rack::Session::Cookie を直接使うように、Sinatraの公式ドキュメントにも書いてあります。だったら、同様にRack::Session::Cookie ではなくRack::Session::Poolを直接呼び出せば大丈夫のような気がします。

ところがPadrinoだと、プロジェクトジェネレータで生成されたアプリケーションのapp/app.rbにおいて、

disable :sessions
use Rack::Session::Pool

などとやろうとすると下記のようなエラーになってしまいます。ちなみにこれはRack::Session::Cookie を使おうとしても同様のエラーになります。

you need to set up a session middleware *before* Rack::Protection::AuthenticityToken (RuntimeError)

これは、Padrinoのデフォルトで Rack::Protection 関係のオプションが有効になっているためです。config/apps.rbの次のような記述が該当します。

Padrino.configure_apps do
  # enable :sessions
  set :session_secret, 'sessionseacretkeystring12345567890abcdef'
  set :protection, :except => :path_traversal
  set :protect_from_csrf, true
end

なので、一旦 Rack::Protection 関係のオプションを無効にし、Rack::Session::Poolを直接呼び出した後にRack::Protectionを直接呼び出します(Rack::Protection 関係のオプションを改めて有効にするとエラーになってしまいます)。app/app.rbのenable :sessionsをコメントアウトし、次の記述を追加します。disable :sessionsは不要かもしれませんが念のため入れてます。

set :protection, false
set :protect_from_csrf, false
disable :sessions
use Rack::Session::Pool, :key => 'rack.session', :path => '/', :expire_after => nil
use Rack::Protection
use Rack::Protection::AuthenticityToken, :authenticity_param => '_csrf_token'

これでエラーが消えました。セッションも、Cookieを確認すると、今までBase64でエンコードされた非常に長い文字列(おそらく暗号化したキー・バリューの組み合わせがセッションIDとともに入っている)だったのが、セッションIDのみらしき文字列に変化しています。上記コードでは有効期限を設定していませんが、指定すればCookieに反映されることは確認できました。なお、 Rack::Protection 関係がきちんど動作しているかどうかは未検証ですのでご注意ください。Rack::Protection::AuthenticityTokenも動作してるのを確認しました。

※2014/05/02 Rack::Protection::AuthenticityTokenの動作を確認したことを追記、コードの use Rack::Protection::AuthenticityToken にトークンのパラメータ名を指定する引数を追加

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。

2014年5月1日木曜日

さあ、IEを捨てよう

さて、IE(Internet Explorer)のVer6〜11という幅広いバージョンに影響のある脆弱性が見つかり、さらにXPはサポート終了につきマイクロソフトは対応する予定も当然ない、というニュースが流れてますね。

いい機会です。もういい加減にIEやめましょう。と言いますかまだIE使ってるなんて勘弁して下さい。ましてやIE6とか、もう化石かと突っ込みたくなります。

もちろん、脆弱性が修正されないまま放置されるPCが多く出現するという状況自体は憂慮すべきことなのかもしれませんが、これを切っ掛けに皆がIEに見切りをつけ、FirefoxやChromeなどの他のブラウザに乗り換えてくれるなら(特にXPユーザが)、Web系エンジニアにとっては大変喜ばしい状況になるという側面も出てくるる気がします。そうなってほしい。

それにしても、IE6の呪縛が本当の意味で終わるまでにこんなに時間がかかるとは・・・。僕の周囲ではかなり消えつつありますが、まだゴキブリ並みのしつこさで生き残っている場所はあるんでしょうねぇ。ほんと、早く消えて欲しいものです。

さあ、今こそIEを捨てる時。ChromeかFirefoxに乗り換えよう。

※この記事について指摘・意見・提案・感想などありましたら下のコメント欄にどうぞ。