Techracho

Rails image_pathの動作が違う

このエントリーをはてなブックマーク Share
2010.06.05    Ruby, Ruby on Rails, 馬場   タグ: —    baba   

Railsで画像のパスを取得したいときは、image_pathを使います。

<%= image_tag 'test.png' %>
<%= image_path 'test.png' %>

これらのヘルパーメソッド、Viewの中で使う分には問題なく動くのですが、Controller内で使うと、prefixが付かない問題が発生します。

たとえば、http://example.com/myapp/ をアプリのルートにしているとき、

View内で使用: /myapp/images/test.png
Controller内で使用:/images/test.png

このように戻り値が違います。

完全にバグとしか言えないのですが、とりあえず修正しないことには仕方ないので、

prefix = ActionController::Base.relative_url_root
path = image_path 'test.png'
return prefix + path

みたいな処理を行うことにしました。ControllerとView両方で呼び出すメソッドでは、判定式も必要になりそうです。

Railsはprefix周りで問題が多いですね。
サーバに明示的な引数まで指定してこれですか・・・と

何も指定しなくても勝手にやってくれるCakePHPは、実はすごく優秀な子だったんだと見直すばかりです。

XAMPPでRailsのProxy

このエントリーをはてなブックマーク Share
2010.05.24    Ruby, ネットワーク, 馬場   タグ: , , —    baba   

Railsで開発する際は、素直にRadRailsのボタンでサーバを起動してしまうのが楽で良いです。
例:http://localhost:3000

しかし、ポート番号ではなく好きな名前でアクセスしたいこともあります。
また、Rails 2.3には、prefix(ドメイン直下以外にアプリを配置)にバグがあるため、それをローカルで試したいこともあるかもしれません。

XAMPPだけで普段はサーバ作業に触れない人には馴染みが薄いかもしれませんが、Apacheなので、たんなるProxyならかんたんです。
例:http://localhost/myapp

<VirtualHost *:80>
	ProxyRequests Off
	<Proxy *>
		Order deny,allow
		Allow from all
	</Proxy>
	ProxyPass /myapp http://localhost:3000
	ProxyPassReverse /myapp http://localhost:3000
</VirtualHost>

なお、肝心のPrefixバグの対策は、システムチームの伊藤がブログを書いていますのでご覧下さい。
http://www.bpsinc.jp/blog/archives/1579

SQLの凡ミスに注意

このエントリーをはてなブックマーク Share
2010.05.12    馬場   タグ: , , —    baba   

MySQLを生で使う際、UPDATE文のちょっとした書き間違えに気をつけましょう。

たとえば、users というテーブルに、id, name, state があったとします。

CREATE TABLE `test`.`users` (
`id` INT NOT NULL ,
`name` VARCHAR( 255 ) NULL ,
`state` TINYINT NULL
);

これを書き換える際、

UPDATE `users` SET `name` = 'yamada', `state` = 1;

とやればOKですが、

UPDATE `users` SET `name` = 'yamada' AND `state` = 1;

とやってしまうと、エラーも何も出ず、予想外の結果になってしまいます(nameが0や1になって、他は変わらない)。

これは、

UPDATE `users` SET `name` = ('yamada' AND `state` = 1);

と解釈されて、「stateが1であり、yamadaという文字列がtrueと認識される」条件式、つまり「stateが1かどうか」の結果がnameに代入されるためです。

当たり前だし単純なのですが、カンマとアンドを書き間違えないように、気をつけましょう。

IE7でmarginが消える

このエントリーをはてなブックマーク Share
2009.06.25    CSS, HTML, 馬場   タグ: , —    baba   

たぶん既出ネタですが、ふと見つけたので一応。
IE7で、marginが消える場合があります。

再現条件は、
・paddingが0以外のブロック要素Aに囲まれている
・Aの最初の子要素(空白・TAB・改行以外)がブロック要素Bである
・Bにheightとmarginが指定してある(たぶんhasLayoutがtrueになるならheight以外でも該当)
こんな感じみたいです。

これらを満たすと、Bのmarginが無視されます。
シンプルな再現HTMLは以下の通りです。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>test</title>
</head>
<body>

<div style="padding:1px;">
<div style="margin:100px; height:20px;">こんにちは</div>
<div style="margin:100px; height:20px;">ハロー</div>
</div>

</body>
</html>

これを開くと、IE7以外では「こんにちは」と「ハロー」の左端はそろいますが、IE7では「こんにちは」が左に寄ってしまいます。

解決するには、Bを先頭子要素でなくせばいいので、適当なコメントを入れます。

<div style="padding:1px;">
<!– bugfix for IE7 –>
<div style="margin:60px; height:20px;">こんにちは</div>
<div style="margin:60px; height:20px;">ハロー</div>
</div>

やっぱりIE7は地雷ですね。
自動更新でIE8が入るようになったのがせめてもの救いです。

Windowsのネットワークプレイスで謎のバグ

このエントリーをはてなブックマーク Share
2009.06.19    Windows, 馬場   タグ: , —    baba   

Windows Vista以降で(XP以前は知りません)、「コンピュータ」で右クリックして「ネットワークの場所を追加」すると、ネットワークフォルダをドライブ文字を消費せずに割り当てられて、お手軽です。

しかし、その名前をある程度長い(5文字以上くらい)日本語にすると、アイコンがドライブではなくフォルダになり、動作もショートカットになってしまうことがあります(開くとtargetというリンクがあり、それを開くとようやくネットワークフォルダにアクセスできる)。
これでは、見た目も悪いし、毎回1クリック増えるのは我慢できません。

もちろん英語名にしても良いのですが、簡単に直ったので紹介します。

日本語名のフォルダが、ネットワークドライブではなくリンクになってしまう

日本語名のフォルダが、ネットワークドライブではなくリンクになってしまう

3文字くらいまでの短い日本語名にすると、直る

3文字くらいまでの短い日本語名にすると、直る

一度直れば、1~2文字追加しても大丈夫

一度直れば、1~2文字追加しても大丈夫

さらに1文字ずつ追加しても大丈夫

さらに1文字ずつ追加しても大丈夫

さらに1文字追加しても大丈夫

さらに1文字追加しても大丈夫

以上のように、「1文字ずつ追加していく」と、なぜか長い日本語名でも大丈夫です。
また、1文字ずつ入れなくても、「ほげほげほげほげほげ」などは平気だったりしました。

どう考えてもバグなのですが、いったい何でこんなことに??
ネットワークの場所自体は、
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\PublishingWizard\AddNetworkPlace\AddNetPlace\LocationMRU
にあるみたいなんですが、表示名はどこに保存されているのか見つからなかったんですよね。。

他の環境での再現する・しない情報などもらえたら嬉しいです。

【再現環境】
Windows 7 Ultimate 64bit RC1
Windows Vista Ultimate SP2 32bit
サーバ: Windows Server 2008 R2 Standard 64bit RC

IE7でposition:relativeの要素が重なるバグ

このエントリーをはてなブックマーク Share
2009.03.03    HTML, javascript, 馬場   タグ: , , , , —    baba   

IE7で、JavaScriptを使って動的に要素を表示・非表示した場合、要素が重なってしまうことがあります。

実験ページ

IE7だと問題が再現し、IE8やFirefoxは再現しません。また、冒頭のDOCTYPE宣言を外してQuarksモードにしても再現しません。
IE7でのみ、Showボタンを押すと、文字が重なってしまうはずです。

不思議なことに、21行目のbackground指定を外すと、再現しません。
また、21行目の<div>開きと22行目の<div>開きの間に「スペース・TAB・改行以外の何か(コメントでも可)」を入れると、再現しません。
22行目の[height:2em] は、hasLayoutがtrueになれば何でもOKです。

まとめると、
・ブロック要素Aがある。
・Aの最初の子要素はブロック要素Bである。
・AはCSSで背景がnone以外に指定されていて、[hasLayout=false]である。
・Bは[position:relative] であり、なおかつ[hasLayout=true]である。

これらの条件を満たす場合、「Aの前にある要素の高さが変わり、AのY座標が変更になっても、BのY座標は再計算されない」というIE7独自のバグと言えそうです。

DebugToolbar等で位置の再計算を行うと、正しい位置に移動します。

おそらく計算順序にバグがあるのだと思います。position:relativeを使うときは気をつけましょう。

XML-RPC

このエントリーをはてなブックマーク Share
2009.02.27    Linux, PHP, Web, 馬場   タグ: , , —    baba   

PHPでXML-RPCを使った記録です。
PEAR::XML_RPC2を使うことにしました。PHP5専用で使いやすそうです。

  • PHP5以上
  • cURLのエクステンション

が必須です。

クライアント側では、

require_once './XML/RPC2/Client.php';
$options = array('debug' => true);
$client = XML_RPC2_Client::create('http://www.bpsinc.jp/xmlrpc.php', $options); //URLはダミー
try {
    $result = $client->Version(); //関数名はサーバ側で定義したもの
    print_r($result);
} catch (XML_RPC2_FaultException $e) {
    var_dump($e);
} catch (Exception $e) {
    var_dump($e);
}

のように使えます。

しかし、これだとなぜか結果が全部NULLになることがあります。
debugメッセージを見ると、Server Responseは正常なのに、Decoded ResultがNULLになっているように見えます。

この問題、Server側かHttpRequestの実装の問題だと思うのですが、Server ResponseのBodyの最初に改行が入っているのが原因でした。
"\n<?xml version="1.0"?><methodResponse> …"

PEAR/XML/RPC2/Backend/Xmlrpcext/Client.php の112行目付近で、
$result = xmlrpc_decode($body, $this->encoding);
となっているところを、
$result = xmlrpc_decode(trim($body), $this->encoding);
とすれば直ります。(マルチバイト対応のtrimを書いた方が良いかもしれません)
※XMLRPCEXT拡張モジュールが入っていない場合は、PEAR/XML/RPC2/Backend/PHP/Client.phpになります。

ライブラリを書き換えるのは強引ですが、xmlrpc_decode関数自体がEXPERIMENTALのままですし、あとはHttpRequestやサーバ側を変更しなければ行けないので、スマートな解決方法は思いつきません。
とりあえずresultがnullになる問題は回避できました。

ListBoxでOutOfMemoryException

このエントリーをはてなブックマーク Share
2009.02.23    C#, Windows, 馬場   タグ: , , , —    baba   

.NETでListBoxにobjectをAddするとき、OutOfMemoryException 「リストボックスに含まれる項目が多すぎます」が発生することがあります。

これは、ToString() の結果がnullになるオブジェクトを挿入したときに起こります。
エラーメッセージがわかりにくいです。
ToString()の結果に変数を返すようなオブジェクトを作るときは、確実に値が入るようにしないとまずいということですね。

C#で便利な例外処理コード

このエントリーをはてなブックマーク Share
2009.02.10    C#, Windows, 馬場   タグ: , , —    baba   

C#でプログラムを書いているとき、エラーが発生したらとりあえずthrowしますよね?
その例外は、どこかでcatchして、メッセージボックスなりエラーログなりで出力しなくてはいけません。
忘れると、例外発生時に情報無しで落ちてしまって、情報収集がしにくくなります。

しかし、開発中にきちんとしたエラー処理を書くのも面倒なので、とりあえず捕捉しなかったエラーは全部1カ所で処理したいものです。
単純にApplication.Run()をtry-catchで囲っても、正しく処理できません。
この場合は、ApplicationのThreadException と ThreadのUnhandledException を使います。

[参考]
適切に処理されなかった例外をキャッチするには?

下記のサンプルコードのようにすると、catchされなかった例外はメッセージボックスを表示し、同じディレクトリのerror.txtに詳細が記録されます。
開発中でエラーポリシーも詳細が決まっていないとき、とりあえずこのような処理を入れておくと、デバッグモード以外で起動してもエラー詳細が見られて便利です。


//サンプルコード
[STAThread]
static void Main()
{
    //エラーハンドラを登録
    Application.ThreadException += new System.Threading.ThreadExceptionEventHandler(Application_ThreadException);
    Thread.GetDomain().UnhandledException += new UnhandledExceptionEventHandler(Program_UnhandledException);

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

static void Program_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    Exception ex = e.ExceptionObject as Exception;
    if (ex != null)
    {
        ShowError(ex, "UnhandledException");
    }
}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
    ShowError(e.Exception, "ThreadException");
}

static void ShowError(Exception ex, string title)
{
    MessageBox.Show("プログラム中で補足されなかったエラーが発生しました。詳細はエラーログをごらん下さい。", title);

    StreamWriter stream = new StreamWriter("error.txt", true);    
    stream.WriteLine("[" + title + "]");
    stream.WriteLine("[message]\r\n" + ex.Message);
    stream.WriteLine("[source]\r\n" + ex.Source);
    stream.WriteLine("[stacktrace]\r\n" + ex.StackTrace);
    stream.WriteLine();
    stream.Close();
}

オーバーライドでちょっとはまりました

このエントリーをはてなブックマーク Share
2009.01.22    C#, 馬場   タグ: , , , , , —    baba   

C#で System.Xml.XmlDocument を使っています。
XmlDocument xmlDocument = new XmlDocument();

プレフィックス付きのノードを作るとき、たとえば


<s:root xmlns:s="urn:hogehoge-hoge" >
    <s:child />
</s:root>

というXMLを作りたいとして、

XmlNode root = xmlDocument.CreateNode(XmlNodeType.Element, "s", "root", "hogehoge-hoge");
XmlNode child = xmlDocument.CreateNode(XmlNodeType.Element, "s", "child", "hogehoge-hoge");
root.AppendChild(child);

とすると動きますが、毎回NamespaceURIを指定するのは変だと思います。
しかし、最後の引数を指定しないと、prefixが出力されません。

また、
xmlDocument.Save("filename");
でファイルに保存するのは1行ですが、string型で受け取ろうと思うと、

StringWriter writer = new StringWriter();
xmlDocument.Save(writer);
string output = writer.ToString();

としないといけません。

色々不便なので、XmlDocumentを継承したExtendedXmlDocumentを作りました。命名がSXGA並にださいですが。。
このクラス内部では、prefixとnamespaceURIのHashtableを持っていて、一度指定すると以降はnamespaceURIを指定しなくても対応するものを用意してくれます。
また、ToString() をオーバーライドしてあるので、1行で文字列を受け取れます。

・・・と、ここまでは良かったんですが(前置きが長い)、
その後調子に乗ってCreateAttribute()をオーバーライドしたらはまりました。

public override XmlAttribute CreateAttribute(string prefix, string name, string value);
を作って、内部で
base.CreateAttribute(name);
を呼ぶと、StackOverflowExceptionが発生します。

どうやら、XmlDocument.CreateAttribute(string) 内部で CreateAttribute(string, string, string) を呼んでいたみたいです。
C#のオーバーライドは動的にメソッドを呼んでくれるので、派生クラスのCreateAttribute(string, string, string) が呼ばれてしまって、その内部で CreateAttribute(string) を呼ぶと無限ループ、と。

.NET Frameworkの内部コードが見られれば一瞬で解決した問題なんですが。
オーバーライドは慎重に使わないとバグの元ですね。オーバーロードと組み合わさるとたまに危険。気をつけます。

« 新しい投稿

COPYRIGHT [C] 2009 BEYOND PERSPECTIVE SOLUTIONS LTD. ALL RIGHTS RESERVED.