Ruby CGIのオフラインモード

Rubycgiには、オフラインモードがあり、コマンドラインから簡単にcgiを試すことが出来ます。

オフラインモードを使ってみる

まずは、下記の記述があるファイルを作成します。ファイル名は hoge.rb とします。

require 'cgi'
cgi = CGI.new

p cgi.params

オフラインモードで使ってみます。

% irb hoge.rb
hoge.rb(main):001:0> require 'cgi'
=> []
hoge.rb(main):002:0> cgi = CGI.new
(offline mode: enter name=value pairs on standard input)

(offline mode: enter name=value pairs on standard input) となり、入力を求められますので、下記のように入力し、Enterを押します。

hoge=123&fuga=456

Enterを押しただけでは、終了しません。Ctrl-D を押すことで続きのプログラムが実行されます。

=> #<CGI:0xb7c08d40 @output_hidden=nil, @multipart=false, @output_cookies=nil, @params={"fuga"=>["456"], "hoge"=>["123"]}, @cookies={}>
hoge.rb(main):003:0>
hoge.rb(main):004:0* p cgi.params
{"fuga"=>["456"], "hoge"=>["123"]}
=> nil
hoge.rb(main):005:0> %        

CGIをオフラインモードで使うことが出来ました。

実験環境

OS CentOS release 5.3 (Final)
ruby ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-linux]

Symbolでconstantize

Rails の active_support/core_ext/string/inflections.rb で Ruby の String が拡張されて constantize が使えて便利です。

% script/console
Loading development environment (Rails 2.3.11)
>> "Hash".constantize
=> Hash
>> :Hash.constantize
NoMethodError: undefined method `constantize' for :Hash:Symbol
        from (irb):2

constantize を Symbol で使いたいと思い、検索していたら こちらのページを発見しました。以下のように実現しています。

class Symbol 
  def constantize
    self.to_s.camelize.constantize
  end 
end 

使ってみます。

% script/console
Loading development environment (Rails 2.3.11)
>> class Symbol 
>>   def constantize
>>     self.to_s.camelize.constantize
>>   end 
>> end 
=> nil
>>
>> :Hash.constantize
=> Hash
>> :hash.constantize
=> Hash

たしかに出来ますが、camelize は仕事しすぎなのかなと思いました。 String拡張の constantize には「It raises a NameError when the name is not in CamelCase」と記述があるように、"hash".constantize をサポートしていませんし。

154         # +constantize+ tries to find a declared constant with the name specified
155         # in the string. It raises a NameError when the name is not in CamelCase                                                                                                                               
156         # or is not initialized.
157         #
158         # Examples
159         #   "Module".constantize # => Module
160         #   "Class".constantize  # => Class
161         def constantize
162           Inflector.constantize(self)
163         end

なので、camelize は外しました。duck typingをイメージしています。

class Symbol 
  def constantize
    self.to_s.constantize
  end 
end 

使ってみます。

% script/console
Loading development environment (Rails 2.3.11)
>> class Symbol 
>>   def constantize
>>     self.to_s.constantize
>>   end 
>> end 
=> nil
>>
>> :Hash.constantize
=> Hash
>> :hash.constantize
NameError: wrong constant name hash
        from /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.11/lib/active_support/inflector.rb:364:in `const_defined?'
        from /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.11/lib/active_support/inflector.rb:364:in `constantize'
        from /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.11/lib/active_support/inflector.rb:363:in `each'
        from /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.11/lib/active_support/inflector.rb:363:in `constantize'
        from /usr/local/lib/ruby/gems/1.8/gems/activesupport-2.3.11/lib/active_support/core_ext/string/inflections.rb:162:in `constantize'
        from (irb):3:in `constantize'
        from (irb):8

Symbolにconstantize拡張がされないのは、使われないからですかね?もしくは、他に何か意味があるのでしょうか。

環境

ruby ruby 1.8.6 (2010-02-05 patchlevel 399) [i686-linux]
Rails 2.3.11

MySQL の create index で Duplicate key name する条件

いつも、Railsのmigrationにまかせっきりの create index 構文で生成される index名 が重複エラーする条件ってなんだろう・・・ということで調べてみました。

テスト環境

MySQL 4.1.22

前提条件

hoges と fugas というテーブルがあり、それぞれ、idとnameというカラムを持っている。

テスト

hogesのnameカラムにindexを張ります。index_name は hoge_index です。

mysql> create index hoge_index ON hoges(name);
Query OK, 0 rows affected (0.18 sec)
Records: 0  Duplicates: 0  Warnings: 0

当然成功です。次に、もう一度、同じコマンドを実行します。

mysql> create index hoge_index ON hoges(name);
ERROR 1061 (42000): Duplicate key name 'hoge_index'

Duplicate key name です。同じテーブルで同じ名前のindex_name は使えません。
次に、fugasのnameカラムにindexを張ります。index_name は hoge_index です。

mysql> create index hoge_index ON fugas(name);
Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

indexを張ることが出来ました。同じindex_nameでもテーブルが違えば大丈夫なのですね。
一応、もう一度同じコマンドを打ちます。

mysql> create index hoge_index ON fugas(name);
ERROR 1061 (42000): Duplicate key name 'hoge_index'

もちろん。Duplicate key name です。

Rails2.3をMySQL4.xで使うときのRELEASE SAVEPOINTの問題

MySQL4系ではRELEASE SAVEPOINT をサポートしていないので、Rails2.3がMySQL4.xに向けて、RELEASE SAVEPOINTを使ってもエラーになるという話です。

実験環境

Rails 2.3.11
MySQL 4.1.22

エラーの再現

User.connection.transaction do
  User.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
    User.find(:all)
  end
end  # RELEASE SAVEPOINT active_record_1 

ActiveRecord::StatementInvalid: Mysql::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'RELEASE SAVEPOINT active_record_1' at line 1: RELEASE SAVEPOINT active_record_1
        from /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract_adapter.rb:227:in `log'
        from /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/mysql_adapter.rb:324:in `execute'
        from /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/mysql_adapter.rb:370:in `release_savepoint'
        from /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/database_statements.rb:161:in `transaction'
        from (irb):87
        from /usr/local/lib/ruby/gems/1.8/gems/activerecord-2.3.11/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `transaction'
        from (irb):86

ActiveRecordのコード

activerecord-2.3.11/lib/active_record/connection_adapters/abstract/mysql_adapter.rb

369       def release_savepoint
370         execute("RELEASE SAVEPOINT #{current_savepoint_name}")                                                                                                                                                 
371       end

activerecord-2.3.11/lib/active_record/connection_adapters/abstract/database_statements.rb

153         if outside_transaction?
154           @open_transactions = 0
155         elsif transaction_open
156           decrement_open_transactions
157           begin
158             if open_transactions == 0
159               commit_db_transaction
160             else
161               release_savepoint                                                                                                                                                                               
162             end
163           rescue Exception => database_transaction_rollback
164             if open_transactions == 0
165               rollback_db_transaction
166             else
167               rollback_to_savepoint
168             end
169             raise
170           end
171         end

RELEASE SAVEPOINT を execute で実行していることが分かります。

対応

使えないものは仕方ないので、こちらのように、RELEASE SAVEPOINT を発行しないようにしました。下記の内容を config/initializers/unset_release_savepoint_method.rb に記述しました。

ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
  # Unset release savepoint method 
  def release_savepoint
  end
end 

エラーが再現しないことを確認

User.connection.transaction do
  User.connection.transaction(:requires_new => true) do  # CREATE SAVEPOINT active_record_1
    User.find(:all)
  end
end
=> [#User id・・・・・・・]

もう、MySQL5を使えと言うことですね・・・。

MySQLのSAVEPOINT と ROLLBACK TO SAVEPOINT と RELEASE SAVEPOINT

SAVEPOINTについて知らなかったので調べて、使ってみました。

対象のストレージエンジン

対象バージョン

  • 4.0.14以上
  • 4.1.1以上

実験環境

MySQL 4.1.22

SAVEPOINTの実行例

CREATE TABLE hoges (
 id int(11) NOT NULL auto_increment,
 name varchar(255) default NULL,
 PRIMARY KEY  (id)
) ENGINE=InnoDB
;

BEGIN;
INSERT INTO hoges (name) VALUES ('hoge1');
SAVEPOINT point1;
INSERT INTO hoges (name) VALUES('hoge2');
SAVEPOINT point2;
INSERT INTO hoges (name) VALUES('hoge3');
SAVEPOINT point3;
SELECT * FROM hoges;
+----+-------+
| id | name  |
+----+-------+
|  1 | hoge1 |
|  2 | hoge2 |
|  3 | hoge3 |
+----+-------+
3 rows in set (0.00 sec)


ROLLBACK TO SAVEPOINT point2;
SELECT * FROM hoges;
+----+-------+
| id | name  |
+----+-------+
|  1 | hoge1 |
|  2 | hoge2 |
+----+-------+
2 rows in set (0.00 sec)

COMMIT;

SAVEPOINTをMyISAMで実行してみた

CREATE TABLE hoges (
 id int(11) NOT NULL auto_increment,
 name varchar(255) default NULL,
 PRIMARY KEY  (id)
) ENGINE=MyISAM
;

BEGIN;
INSERT INTO hoges (name) VALUES ('hoge1');
SAVEPOINT point1;
INSERT INTO hoges (name) VALUES('hoge2');
SAVEPOINT point2;
SELECT * FROM hoges;
+----+-------+
| id | name  |
+----+-------+
|  1 | hoge1 |
|  2 | hoge2 |
+----+-------+
2 rows in set (0.00 sec)


ROLLBACK TO SAVEPOINT point1;
SELECT * FROM hoges;
+----+-------+
| id | name  |
+----+-------+
|  1 | hoge1 |
|  2 | hoge2 |
+----+-------+
2 rows in set (0.00 sec)

COMMIT;

SAVEPOINT と ROLLBACK TO SAVEPOINT ではエラーは出ませんでした。ロールバックされないだけでした。

RELEASE SAVEPOINTについて

設定したSAVEPOINTを除外する機能ですが、MySQL4ではサポートされていません。MySQL5.0からの機能です。

Rails勉強会@東京第60回に参加してきました

Rails勉強会@東京第60回に参加してきました。学んだことをメモします。

性能プロファイラ

笹田研の学生さんによるリアルタイムプロファイラの紹介がありました。「リアルタイム」なのが特徴です。rdocを実行してそのプロファイラをざーっと画面に出してデモしていました。普段リアルタイムで監視画面をじーっと見るということは、なかなか少ないとは思いますが、テストの時や裏でずっと実行していて「しきい値」を設けてアラート出すとか、スナップショットを定期的に取ってグラフ化するなどの方がWebアプリを作る側としては実用的かもと感じました。しかし、私は普段はアプリのプロファイリングというもの行っていないので、RubyProf等を使って一度やってみようと思いました。

kaminari

a_matsudaさんによるkaminariについての直々のプレゼンです。とても貴重なプレゼンでした。kaminariの基本的な使い方、ソースコードを読みながらの仕組みの説明でした。kaminariはカスタマイズ性がとても高いなと感じました。will_paginatekaminariかRails3を使うときは悩みそうです。engineアプリというのを初めて知りました。kaminariはすでに海外のプロダクションで使われているということです。また、a_matsudaさんはkaminariはドキュメントを沢山書いたと言っていました。そして、ドキュメントを沢山書いたほうが使ってもらえる傾向があるので、使ってもらいたいならドキュメント書くと良いとも言っていました。

Model Test

  • test/unit
  • test/unit2
  • rspec
  • Shoulda

Model Testは相変わらず test/unitとrspecの2本柱ですね。今回はテストについての話が多かったと思いました。「Ruby遅い」「Rails遅い」は最近は「テストが遅いこと」と関連しているという話が印象的でした。

機能テスト

  • functional (Rails付属の単体コントローラテスト)
  • Cucumber

両方使うのではなく、どちらかを使うというのが印象的でした。test/unit と functional、rspec と cucumber という組み合わせに分かれていた感じです。

CI(継続的インテグレーション

BigTunaという存在を初めて知りました。BigTunaはRails3で作られていて、機能も豊富そうです。

テストカバレッジ

皆さんテストカバレッジについて良いツールを探しているような印象でした。単純なコード通過を計測するのではなく、例えば以下のような例についてはツールではカバーしきれない部分があるようです。

def hoge
  if fuga
    ・・・
  end

  if baz
   ・・・
  end
end

hogeメソッド内には2つのif文がある。単純なコードカバレッジであれば、fugaもbazもtrueのものを1つテストすれば100%のカバレッジになる。しかし、上記の例だと最大で下記の4パターンをテストする必要がある。

fuga baz
true true
true false
false true
false false

上記の場合、皆さんツールを使うのではなく「経験則」「勘」で「大事そうなパターンのみ」を行っている感じでした。このテストカバレッジについては、私も課題です。

テストの高速化

「テストが遅い」というのは皆さん共通の悩みのようでした。弊社でも並列テストしているのですが落ちるのですよね・・・。

懇親会

Rails勉強会の後、居酒屋で数人で懇親会を行いました。メンバーは@satococoa@mreinsch@kyanny@hamaknと私でした。それぞれの開発現場について聞けたり、悩みについて聞けたりして、使っているツールや、RailsRubyのバージョン、pivotaltrakerでのタスク管理について聞けたり、バージョン管理について聞けたり、などなど1つのテーブルでしたし、皆でざっくばらんに楽しく会話できました。Rails勉強会で1つのテーマについて皆で話すのも良いですが、沢山のテーマを短く気軽に、そしてジョークを言いながら、笑いながら話し合えるのは懇親会の良い点だなと思いました。少人数でもよいので懇親会は行いたいなと思いました。懇親会が出来てよかったです。有難う御座いました。

その他、Rails勉強会@東京のFacebookページの存在やQuoraというサイトに招待してもらったり、Tokyo Rubyist Meetup | Doorkeeperという存在を知ったりできました。

クリーンなコードはプログラマを豊かにする

これは、「理解することが書き直すことを意味するとき」から引用したグラフ。

プログラマは何に一番時間を使っていると思う?「コードを新規に書くこと?」「コードを修正すること?」いやいや「コードを理解すること」。グラフでは「コードを理解すること」に70%使う。私も感覚値ではこのくらいかな。実際は設計を考えたりする時間もあるけど、今回はコードを読み書きするところに焦点をあてる。

新規のコードを書いて、少しコーヒー飲んで一服してくると、すぐに古いコードになる。自分で書いたコードを再度読んで理解して、コードを追加したり、修正したりする。このサイクルが続くとコードが増えて読み理解する量がどんどん増える。
既存のコードを書き換えるのは言うまでもなく、まず、コードを理解しないと修正できない。過去自分が書いたコードならまだしも、他人が書いたコードを理解するのは本当に時間がかかる。

「理解する」とは、つまり「コードを読むこと」。汚いコードを読むのはとても大変。動くけど、汚いコードというは「負の遺産」とまで言われる。汚いコードは将来の「理解する」時間を余計に取る、つまり負債となる。これが、「負の遺産」といわれる所以じゃないかと思う。

仕事の大半、つまりプログラマの仕事の大半は「理解すること」といっても過言ではないと思う。これはもしかしたら、営業や経理といった他の仕事にも当てはまるんじゃないかな。

この「理解する時間」を短縮することが、プログラマの人生を豊かにする上で重要なことじゃないかと感じる。「なんで動くのに修正するのさ」ということも理解できるし、コードレビューやリファクタリングが大切なのもこれで分かる。

「クリーンなコードはプログラマを豊かにする」

じゃあ、クリーンなコードってなんなのさ。というのは、たくさんクリーンなコードを読んで理解していくしかないと思う。