Coding for Fun

ruby on railsを中心としたウェブ技術つれづれ日記

Rubyコードを他人に使ってもらう

簡単なRubyコードを他人に使ってもらいたいことがしばしばあります。CSVの洗浄だったり、スクレイピングだったりするスクリプトです。そういうときに使えるTips

Rubyをインストールしてもらう

開発者の負担:低
利用者の負担:高
ワンクリックインストーラーがあるとはいえ、CSVの洗浄のためにRubyをインストールするのはちょっと。実行もコマンドプロンプトを立ち上げるのか、あるいはバッチファイルを作るのか、ですが、どちらも初心者には不安が残る方法です。ただ、状況によってはこれが一番いい選択肢のときもあります。かなりITリテラシーが高い人なら、コードを直接編集できますからね。

Rubysscript2exeを使う

https://github.com/ryanbooker/rubyscript2exe
開発者の負担:中
利用者の負担:中
Rubyインタープリタとコードをexeファイルにまとめてくれるものです。まあまあ使いやすいけど、マックとかで使えないのと、GUIがないのがネック。

jRuby & Swingを使う

開発者の負担:高
利用者の負担:低
クロスプラットフォームでつかえると言えばJavaRubyにはjRubyというすばらしいプロジェクトがありますから、それをつかえば、大した手間なくGUIアプリができます。「GO!」ボタンだけがあるGUIアプリでも、バッチのスクリプトよりは初心者にとってずっと安心できるでしょう。
ちなみに、最近はSwingの後継であるJavaFXが流行っていますが、私の環境だと動いてくれなかったので、残念ながらあきらめました。

jRuby & Swingで配布できるような実行ファイルを作るまで。

jRubyのインストール

rvmがあったら簡単。rbenvより私は好きです。

rvm update
rvm install jruby
rawrのインストール

rawrというのは、jRubyをコンパイルするためのgemです。普通にインストールするとRuby 1.8系列用のものが入ってしまうので作者のサイトからインストールします。

gem install rawr --source http://gems.neurogami.com
rawrのセットアップ

GUIのプロジェクトを作ります。

mkdir HelloWorldGui
cd HelloWorldGui
rawr install
アプリ開発

src/main.rb が編集すべきファイルです。以下の通り書いてください。

require "java"

java_import javax.swing.JFrame
java_import javax.swing.JButton
java_import javax.swing.JOptionPane

class HelloWorld < JFrame
  def initialize
    super "Example"
    
    setSize(150, 100)
    setDefaultCloseOperation(JFrame::EXIT_ON_CLOSE)
    setLocationRelativeTo(nil)
    
    button = JButton.new("Say Hello")
    add(button)
    
    button.addActionListener do |e|
      JOptionPane.showMessageDialog(nil, "Hello World")
    end
    
    setVisible(true)
  end
end

HelloWorld.new

コンパイル

以下のコマンドでコンパイルすれば完成です。

rake rawr:bundle:exe
rake rawr:bundle:app

先週一週間の生産量=コード一行

先週一週間の私の仕事をgit diffで:

-    tol = 1e-12;
+    tol = 1e-16;


Matlabで書いた割と複雑なシミュレーションのコードが、ときどきあり得ない値を出すことを発見しました。大量に近似解とか局所最適化とか使っているので、疑わしいことばかりです。

バグハンティングを一週間本気で、残業して、土日返上でやって、やっと判明。最適化の収束判定条件が1e-12(=0.0000000001)では甘くて、1e-16(=0.0000000000000001)でなくてはならなかったようです。

近似の有効数字がたかだか10桁くらいかと思って油断しました。たぶん入力によっては一五桁くらいまで行くんでしょう。私のシミュレーション繊細すぎだって。

今日の反省ポイント(というか願望)

  • 有効数字の安全マージンは多めに取るべき。遅くなるけど、一週間デバッグするよりまし。
  • Matlabでもテストたくさん書くべき。Rspec的なものがあればいいのに。ていうかRubyが恋しい。遅いから使うのは無理だけど。

Rubyで大きなファイルを編集する

あるフォルダ以下のファイルに関して、正規表現とか使って内容を少し変更したいことがあります。
そういうときの方法についてメモ。

Dir.glob("./path-to-folder/**/*.rb") do |file|
  Tempfile.open('foo') do |tf|
    IO.foreach(file) do |line|
      line = line.gsub(...)
      tf.write line
    end

    tf.close
    FileUtils.copy_file tf.path, file
  end
end

Rubyは実に美しい。

ポイント
  • Dir.globを使うと、特定のフォルダ以下のファイルを検索できる。**を使うと、サブフォルダ以下を再帰的に探していく。
  • IO.foreachを使うと、行ごとに編集できるから、メモリに収まらないような巨大なファイルでも編集できる。
  • とりあえず一時ファイルに保存して、その後上書きすればよい。直接ファイルを編集することはできないらしい(参考)

Ruby on RailsからJavaへの引っ越しはやっぱり大変

先日の記事(デスクトップアプリ開発してみる)の続きです。とりあえずJava/GroovyのGriffonっていうフレームワークを使ってみることにしました。



インストールそのものはオフィシャルサイトに従って行ったら簡単でした。
環境変数の設定の仕方とか忘れててちょっと手間取りましたが。
http://griffon.codehaus.org/Installing+Griffon

Hello world!までは順調に進んだので、続いて開発環境を整えようとしたら、これがまた泣きそうに大変でした。

Eclipse

JavaといえばEclipseでしょう!とりあえずダウンロードしてインストールしてみたけど、よくわからない。ワークスペースとプロジェクトって何?さっき作ったHello world!アプリのフォルダを追加しようとしても、「ワークスペースがありません」「プロジェクトがありません」「プロジェクトのフォルダ構造が不正です」とか言われるし・・・。
悪戦苦闘の後、なんとかフォルダは作成できましたが、コンパイルができません。そもそもGriffonのプラグインってどうやっていれるの?環境変数の設定?どこで?
エラーメッセージをGoogle先生で調べてみても意味が分からない。あきらめました。

Netbeans

やっぱりJavaの本家本元Oracleが作ってるNetbeansだよね!とりあえず最低構成のものをダウンロードしてインストールしてみたけど、これもよくわからない。どうやってもGriffonのプラグインが入ってくれません。フルセットでダウンロードしてインストールし直してもだめ。バージョンが新しすぎるのかと思って、7.2.1の代わりに7.2.0を使ってみてもだめ。Griffonプラグインで、サポート掲示板にある最新のビルドを使ってみてもだめ。

IntelliJ IDEA

泣きそうになって、調べまくると、IntelliJ IDEAというのがGroovyのサポートに定評があるとのことです。それを使ってみたら、あっさりできました。


GUIアプリの準備整ったぞ!と思ったけど、次はGUIフレームワークを選ばなければならないそうです。

Swing

古い。1995年作成。使いづらい。リソースすごく多い。

JavaFX

新しい。使いやすい。リソースやや少ない。これからのオフィシャル。


やっぱり技術者は最新版に飛びつくものだよね、と思って、JavaFXをダウンロードしてみようとしたものの、私のマック(Snow Leopard)では対応していないらしいです。
いつものStack overflowを参考にインストーラーをハックして、なんとかごまかしてインストール。


これで、コーディングとは関係ない長く険しい道のりを踏み越え、やっとアプリがIDEから起動し、Hello worldが出来るようになりました。さてコーディング、とおもったものの、まだテスト方法を知らないじゃないか・・・。なんと・・・。私はRailsにTDDじゃないコーディングができない体にされているので、自動テストなしでは一行たりともコードがかけません。

Railsで自動テスト環境を整えたときは丸二日くらいかかったけど、あれをまた繰り返すのか・・・。もうこの際GUIテストはあきらめれば、なんとかなるだろう。autotest4jとかになるのかな。


autotest4jはeclipseのみ?え・・・。

ということで、続きます。

Web系開発者がデスクトップアプリを開発しようとするときの10の選択肢

友達にちょっとしたデータ分析機能を頼まれました。エクセルで使えるようにして欲しい、とのことで、10年ぶりくらいにエクセルVBAを見てみたのですが、Ruby on Railsで甘やかされて育った私には、MVCもクロージャーもTDDもイテレーターもない、VBAの言語仕様と開発環境に耐えられませんでした。いっそのことだからまともなデスクトップアプリ作っちゃうか、作ったことないし、と無謀な挑戦を始めました。

どんなアプリでも、一番面倒かつユーザーの評判に直結するのはUI部分の作り込みなので、それを重視して選びました。あとは配布の容易さですね。時代はHTML5だし〜、HTML5のフレームワークを使えばウェブ技術そのまま使えるしそんなむずかしくないよね、デザインはThemeforestでいいし、とか思ったら見通し甘過ぎ。私の迷走をシェアします。


tidesdK

http://www.tidesdk.org/
Titanium Desktopがコミュニティメンテになったもので、HTML5系では最有力です。モダンで良さそうな感じ。Rubyもつかえるし、CSSjQueryが使えるのは、UIが楽そうで素晴らしいです。ただ、私の環境だとそもそもSDKが起動しなかった。あと、実績も心配。プラットフォームの癖とその対応方法が蓄積されてなさそうです。筋はすごくいいと思うので、半年か一年後にもう一度見てみます。一年後にコミュニティがまだ元気だったら、使ってみるかも。



残念ながら他にはHTML5フレームワークはないようです。

ChroniumEnbeddedを使ったオレオレHTML5フレームワーク

http://code.google.com/p/chromiumembedded/
ないならつくればいいじゃない。Stack Overflowでお薦めされていて、それも有りかな、と思いましたが、Rubyラッパーがないことにまずやる気をそがれ、Pythonで頑張るかと気を取り直したものの、やればやるほどtidesdk(の品質が低い模造品)に近づく気がして、あほらしくなってやめました。Do not reinvent the wheel。



気を取り直して別の道を探してみましょう。

.Net系の言語

ウィンドウズだと一番の選択肢です。C#のよい評判はよく聞きます。また、Visual Studioも昔ちょっと触った限りでは、いい感じです。ただ、私の環境はMacで、彼はwindowsなのでだめ。MacやLinux用.Netのオープンソース実装Monoはありますが、いい評判を聞きません。あと、コンパイラにお金を払うのは個人的には嫌いです。そのクローズドな感じがちょっと。PC変えるとお金払わなくちゃいけないのは現実不便だし。


MacCocoa

マック専用だと一番の選択肢ですが、彼はウィンドウズだからだめ。あと、MacCocoaは使いづらい、ってgithub for macを作った人が言っていました。


Adobe Air

クロスプラットフォームでカジュアルなアプリだとこれが一番多そうですが、あまりにクローズドな感じでちょっと。IDEが高くて趣味に合わない、と言うのが致命的。そもそもAdobeは本物のアプリをAirで作っているわけ?Eat your own dogfood, men.


RubyGUIプラットフォーム(Shoes, Visual Rubyとか)

Rubyは大好きなので、これができたら一番なんですが、スタンドアロンにするのが面倒そうです。それなりに大きなデータなので速度も不安。あと、Ruby系のGUIプラットフォームのオフィシャルサイトは、「ルック&フィールを改善するためのツールなんだから、もうちょっとサイトそのもののルック&フィールも考えた方がよろしいのではないでしょうか。」的な感じで、ちょっと・・・。

Visual Ruby
http://visualruby.net/

Shoes
http://shoesrb.com/



ここまで調査して、「これもだめ、これもだめ」ばかりでかなり疲れました。やっぱりメジャーなところじゃないとさまざまな問題が発生することが分かり、5年前に二週間やったきりのJava大陸に進出することを決意。

jRubyGUIフレームワーク

https://github.com/jruby/jruby/wiki/GUIFrameworks
これで、CRubyにあった配布の問題は解決します。ただ、やっぱりサイトがかっこわるいのは変わらず。完成度が低そうで、手を出すのが怖いです。ただでさえJavaは経験がないのに、フレームワークのバグと、IDEの設定ミスと、自分のバグを切り分けるのに苦労しそうです。


Griffon

http://griffon.codehaus.org/
もうRubyはすっぱり忘れて、ちゃんとJavaることにしました。Ruby on Railsを元にしたGrailsを元にした、デスクトップアプリフレームワークGriffonです。MVCとか、設定より規約とか、なんか覚えのあるフレーズがたくさんあって、安心感があります。Rubyを元にしたGroovyを使っているので、言語もそんなにJavaっぽくなくていい感じ。
参考書が出版されるくらいにはコミュニティも成熟しているようですし、とりあえずこれでいきます。つづく、かも。

Railsの簡単なデバッグ

Railsデバッグにはいろいろな方法があります。 Ruby Debuggerを使ってもいいし、IDE内蔵デバッガを使ってもいいでしょう。原始的な方法として、p, pp , putsあたりをつかってコンソールに出力する、というのもあります。複雑なのは使いこなせれば便利だろうと思って、Rails Guideのデバッグセクション(英語)を頑張って読んでそのままやってみようとしました。

ただ、私の環境では、Ruby Debuggerを入れたらRailsが起動しなくなってしまって、あきらめました。複雑なフックがかかっているようで、目の前の問題を解いても、今後また相性問題が発生しそうだな、と感じて。



原始的なp, pp, putsはかなり強力でもありますが、多用するとどの出力がどこから来ているのか分からなくなります。このtrueは何だっけ?あと、デバッグが終わった後に消し忘れることもあります。本番ログに怪しげな数字が大量に出力されていて気づくとか笑えない。

pの使いやすさはそのままに、機能を追加した新しいコマンドを作りました。名付けてpl *1。これは、現在のファイル名、メソッド名、行数を出力してから、変数を出力する単純なメソッドです。

以下のコマンドをpl.rbと名前を付けて、config/initializersフォルダに置いてください。

require 'pp'

def pl(*args)
  str0 = "=== " + caller.select{|l| l.match(/Rails.root/)}.first

  str_out = args.inject(str0){|strs, obj| strs + obj.pretty_inspect}

  puts str_out
  str_out
end


使い方

pl #=> === /Users/.../app/models/user.rb:24 'true_friends'

pl current_user.name, current_user.friends.all
#=>
=== /Users/.../app/models/user.rb:line:24 'true_friends'
User1
[<User...>, 
 <User...>,
...]

*1:print line. 最も使うメソッドなので短いメソッド名にしました。

ちょっと複雑なファクトリーメソッドのテンプレート

Railsでアソシエーションとバリデーションが複雑に絡み合ったモデルを保存するときには、モデルにファクトリーメソッドを作るとよいと言われています。そういうときの要求はいくつかあります。

  • バリデーションに失敗したときは、たとえアソシエーションの奥で失敗したとしても、そのことを教えてほしい。
  • 500ではなくて、エラーはちゃんと綺麗に教えてほしい。
  • どこかで失敗したら全部キャンセルしてほしい。

こういう中級イディオムを見つけ出すのは時間がかかりますが、とりあえずの物を書いておきます。create_with_managerメソッドがファクトリーメソッドで、バリデーションに応じて真偽値を返します。失敗したときは理由をstore.errorsに保存します。

class Store < ActiveRecord::Base
  has_many :manager_stores
  has_many :managers, :through => :manager_stores
  validates :managers, :presence => true

  def create_with_manager(manager)
    Store.transaction do
      begin
        manager.save!
        save(:validate => false)
        add_manager(manager)
        save!
      rescue => e
        errors.add(:base, e)
        raise ActiveRecord::Rollback
      end
    end
  end


  def add_manager(manager)
     (失敗したらエラーを起こす複雑なメソッド)
  end

end


基本的にはsave!で失敗したらエラーを起こすようにする。でも、これだとバリデーションエラーは一度に一つしか発見できません。入力に複数のエラーがあったときまとめてエラーを表示したいなら、

unless manager.save
  errors.add(:manager, manager.errors)
end

...

if errors.length > 0
  raise ActiveRecord:Rollback
end

とかやると、失敗したときにすべてのエラーをコントローラーに返せる。
前のsave!の成功に次のsave!が依存しているようなときは、あとのエラーの解釈に困るから注意。