Ruby CGIのオフラインモード
Rubyのcgiには、オフラインモードがあり、コマンドラインから簡単に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をオフラインモードで使うことが出来ました。
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拡張がされないのは、使われないからですかね?もしくは、他に何か意味があるのでしょうか。
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を使ってもエラーになるという話です。
エラーの再現
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について知らなかったので調べて、使ってみました。
SAVEPOINTとは
対象のストレージエンジン
- InnoDB
- DBD
対象バージョン
- 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_paginateかkaminariかRails3を使うときは悩みそうです。engineアプリというのを初めて知りました。kaminariはすでに海外のプロダクションで使われているということです。また、a_matsudaさんはkaminariはドキュメントを沢山書いたと言っていました。そして、ドキュメントを沢山書いたほうが使ってもらえる傾向があるので、使ってもらいたいならドキュメント書くと良いとも言っていました。
Model Test
- test/unit
- test/unit2
- rspec
- Shoulda
Model Testは相変わらず test/unitとrspecの2本柱ですね。今回はテストについての話が多かったと思いました。「Ruby遅い」「Rails遅い」は最近は「テストが遅いこと」と関連しているという話が印象的でした。
機能テスト
両方使うのではなく、どちらかを使うというのが印象的でした。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 |
上記の場合、皆さんツールを使うのではなく「経験則」「勘」で「大事そうなパターンのみ」を行っている感じでした。このテストカバレッジについては、私も課題です。
テストの高速化
- GitHub - grosser/parallel_tests: Rails: 2 CPUs = 2x Testing Speed for RSpec, Test::Unit and Cucumber
- https://github.com/sgrove/parallel_specs
- https://github.com/timcharper/spork
「テストが遅い」というのは皆さん共通の悩みのようでした。弊社でも並列テストしているのですが落ちるのですよね・・・。
fixture replacement
- GitHub - thoughtbot/factory_bot: A library for setting up Ruby objects as test data.
- GitHub - notahat/machinist: Fixtures aren't fun. Machinist is.
- GitHub - paulelliott/fabrication: Generating Ruby object instances since 2010
- GitHub - sevenwire/forgery: Easy and customizable generation of forged data.
モック
- GitHub - bblimke/webmock: Library for stubbing and setting expectations on HTTP requests in Ruby.
- GitHub - btakita/rr: RR (Double Ruby) is a test double framework that features a rich selection of double techniques and a terse syntax.
- GitHub - floehopper/mocha: Mocha is a mocking and stubbing library for Ruby (canonical repo is now at http://github.com/freerange/mocha)
その他登場したツールやWebサイト、単語
- The Ruby Toolbox - Know your options!
- Cloud Application Platform | Heroku
- GitHub - tmm1/rbtrace: like strace, but for ruby code
- 竹内関数
- はじめる! Rails3(1)
- WEB+DB PRESS Vol.61
- http://jp.newrelic.com/
- http://ruby-prof.rubyforge.org/
- プログラミング言語 Ruby リファレンスマニュアル
- GitHub - tmm1/rbtrace: like strace, but for ruby code
- Selenium - Web Browser Automation
懇親会
Rails勉強会の後、居酒屋で数人で懇親会を行いました。メンバーは@satococoa、@mreinsch、@kyanny、@hamaknと私でした。それぞれの開発現場について聞けたり、悩みについて聞けたりして、使っているツールや、Rails・Rubyのバージョン、pivotaltrakerでのタスク管理について聞けたり、バージョン管理について聞けたり、などなど1つのテーブルでしたし、皆でざっくばらんに楽しく会話できました。Rails勉強会で1つのテーマについて皆で話すのも良いですが、沢山のテーマを短く気軽に、そしてジョークを言いながら、笑いながら話し合えるのは懇親会の良い点だなと思いました。少人数でもよいので懇親会は行いたいなと思いました。懇親会が出来てよかったです。有難う御座いました。
その他、Rails勉強会@東京のFacebookページの存在やQuoraというサイトに招待してもらったり、Tokyo Rubyist Meetup | Doorkeeperという存在を知ったりできました。
クリーンなコードはプログラマを豊かにする
これは、「理解することが書き直すことを意味するとき」から引用したグラフ。
プログラマは何に一番時間を使っていると思う?「コードを新規に書くこと?」「コードを修正すること?」いやいや「コードを理解すること」。グラフでは「コードを理解すること」に70%使う。私も感覚値ではこのくらいかな。実際は設計を考えたりする時間もあるけど、今回はコードを読み書きするところに焦点をあてる。
新規のコードを書いて、少しコーヒー飲んで一服してくると、すぐに古いコードになる。自分で書いたコードを再度読んで理解して、コードを追加したり、修正したりする。このサイクルが続くとコードが増えて読み理解する量がどんどん増える。
既存のコードを書き換えるのは言うまでもなく、まず、コードを理解しないと修正できない。過去自分が書いたコードならまだしも、他人が書いたコードを理解するのは本当に時間がかかる。
「理解する」とは、つまり「コードを読むこと」。汚いコードを読むのはとても大変。動くけど、汚いコードというは「負の遺産」とまで言われる。汚いコードは将来の「理解する」時間を余計に取る、つまり負債となる。これが、「負の遺産」といわれる所以じゃないかと思う。
仕事の大半、つまりプログラマの仕事の大半は「理解すること」といっても過言ではないと思う。これはもしかしたら、営業や経理といった他の仕事にも当てはまるんじゃないかな。
この「理解する時間」を短縮することが、プログラマの人生を豊かにする上で重要なことじゃないかと感じる。「なんで動くのに修正するのさ」ということも理解できるし、コードレビューやリファクタリングが大切なのもこれで分かる。
「クリーンなコードはプログラマを豊かにする」
じゃあ、クリーンなコードってなんなのさ。というのは、たくさんクリーンなコードを読んで理解していくしかないと思う。