Hatena::ブログ(Diary)

I’m a Bomb!

2010-05-18

CDBIでハマる

相変わらず、今更CDBIかという、誠に恐縮でございますが。

[error] Couldn't render template "undef error - DBD::ODBC::st execute failed: [M
icrosoft][ODBC Microsoft Access Driver] パラメータが少なすぎます。2 を指定してく
ださい。 (SQL-07002)(DBD: st_execute/SQLExecute err=-1) [for Statement "SELECT d
ate_added, author_removed, serial_number_id, author_added, date_removed, imp_typ
e_id
FROM   Imp_type_issued
WHERE  imp_type_issued_id=?
" with ParamValues: 1='16'] at C:/xampp/perl/site/lib/DBIx/ContextualFetch.pm li
ne 52.
"

というエラーが出て、死ぬほど調べまわってあーでもないこーでもないした結果、データベース側のカラム名の打ち込みがプアだった罠。

date_removedがdate_ermovedになってた。

"パラメータが少なすぎます。"で調べると真っ先に「カラム名が間違っていないか確認しろ!」とさんざん出てくるのに、あらぬ方向を調べ続けて時間を無駄にした自戒を込めてメモ。

2010-05-15

3つの<select>を連動させる [Perl][Catalyst][Prototype][Ajax]

3つの<select>を使って、次のようなことをしたい。

最初のselectで地方名を選ぶと、2番目の<select>に県名が表示され、2番目の<select>で県名を選ぶと、3番目の<select>に区市町村名が表示される。これを画面遷移なしで行いたい。

地方名→県名の2段連動なら、prototype.jsのobserve_fieldを使う。catalystからprototypeを使うやり方はこちらが参考になりました。

Gosuke Miyashita

で、具体的にはtemplateに下記のように書き加える。

<head>
〜中略〜
<script type="text/javascript" src="/prototype.js"></script>
〜中略〜
</head>
<body>
〜中略〜
<select id="Region" size=20 style="width:150px;" multiple>
       <option value="1">北海道
       <option value="2">東北
       <option value="3">関東
               :
               :
</select>
[% c.prototype.observe_field( 'Region', {
               update  => 'div_pref'
               url     => c.req.base _ 'list/pref',
               with    => "'body='+value",
}) %]
<div id="div_pref">
<select id="pref" size=20 style="width:150px;" multiple>
</select>
</div>
〜中略〜
</body>

これで、地方を選ぶと、list/prefアクションが呼ばれるので、下記のようにコントローラで地方名を受け取り、それを元に県名リストを作成して、置き換える<select>を返す。

my @regions     = split(/,/, $c->req->param('Region'));

テンプレートはこう。(<option>だけ渡せばいいような気がするけど、<select>丸ごと置き換えないとうまく行かなかったので、こうなってます)

<select id="pref" size=20 style="width:150px;" multiple>
[% FOREACH i IN results %]
<option value=[% i.pref_id %]>[% i.pref_name %]</option>
[% END %]
</select>

しかし、地方名→県名→区市町村名の3段連動となると、うまく動かなかった。単純に[% c.prototype.observe_field %]を増やすだけではだめなようだ。いろいろ調べてみたが、いまいちスパッと答えが見つからず。

仕組みとしては、おそらくDOMツリーが構築された後に、<div id="div_pref">の中身をAjaxで書き換えているが、observe_fieldが監視しているのは書き換えられる前のDOMツリーなので、反応しないんジャマイカ。Ajaxの基礎ができてないので、用語とかいろいろ間違ってるかもしれないが。

で、試行錯誤した結果、次のようにすることで目的は達成できた。

2回目のobserve_field(県名の変化の検知)が、1回目のobserve_field(地方名の変化の検知)のときに発動するように、[% c.prototype.observe_field %]が出力するスクリプトをまねして、直にjavascriptを書いた。

<head>
〜中略〜
<script type="text/javascript" src="/prototype.js"></script>
〜中略〜
</head>
<body>
〜中略〜
<select id="region" size=20 style="width:150px;" multiple>
       <option value="1">北海道
       <option value="2">東北
       <option value="3">関東
               :
               :
</select>
<script type="text/javascript">
<!--
function Observe_pref(element, value) {
       new Ajax.Updater(
               'div_cities',
               '[% c.req.base %]list/cities',
               {
                       parameters: 'pref='+value,
                       asynchronous: 1,
                       onComplete: function(request) {
                               // ここに付け足したら4段目もいけるかな?
                       }
               }
       )
}
function Observe_region(element, value) {
       new Ajax.Updater(
               'div_pref',
               '[% c.req.base %]list/pref',
               {
                       parameters: 'region='+value,
                       asynchronous: 1,
                       onComplete: function(request) {
                               new Form.Element.EventObserver( 'pref',
                                       function( element, value ) {
                                               Observe_pref(element, value);
                                       }
                               );
                       }
               }
       )
}
new Form.Element.EventObserver( 'region',
       function( element, value ) {
               Observe_region(element, value);
       }
);
//-->
<div id="div_pref">
<select id="Pref" size=20 style="width:150px;" multiple>
</select>
</div>
<div id="div_cities">
<select id="City" size=20 style="width:150px;" multiple>
</select>
</div>〜中略〜
</body>

以下、参考にさせていただいたサイト。

続きを読む

2010-04-18

Active Directoryと連携して、svn:authorに氏名を表示する(Windows)

属性を変更できるようにする

デフォルトでは、svn:logをはじめとした属性を後から変えることはできないようになっているので、まずここを変更する。

Subversionサーバリポジトリの実体があるディレクトリに、hooksディレクトリがあるので、その中のpre-revprop-change.batを作成する。

rem  [1] REPOS-PATH   (the path to this repository)
rem  [2] REVISION     (the revision being tweaked)
rem  [3] USER         (the username of the person tweaking the property)
rem  [4] PROPNAME     (the property being set on the revision)
rem  [STDIN] PROPVAL  ** the property value is passed via STDIN.
if "%4"=="svn:author" (
    exit 0
)
exit 1

フックスクリプト(post-commit)で変更する

下記は、perlで行う場合の例。まずバッチファイルでperlスクリプトをキックする。

rem  [1] REPOS-PATH   (the path to this repository)
rem  [2] REVISION     (the revision being tweaked)

set REPOS=%1
set REV=%2

C:\Perl\bin\perl D:\testrepository\hooks\ldap.pl %REPOS% %REV%

exit 0

rem http://subversion.apache.org/faq.ja.html#hook-debugging
rem Subversion はフックスクリプトを起動する前に、全ての環境変数を取り除く。
rem その中には、Unixでは $PATHが、Windows では %PATH% が含まれる。
rem 結果、スクリプトでは、絶対パス名の記述された他のプログラムだけを実行可能だ。

フックで呼び出されたら、実際の処理を行う。

実際の処理部分は、下記の通り。

おおざっぱに言うと、svnlookコマンドでsvn:authorの値を取り出して、Active DirectoryLDAPで問い合わせをして、氏名を割り出し、svn propsetで当該のリビジョンに埋め込んでいる。

use strict;
use warnings;

use Encode;
use Net::LDAP;

my ($repos, $rev)       = @ARGV;

my $cn                  = "--USERNAME--";                                                       # LDAPアクセス用ID
my $pass                = "--PASSWORD--";                                               # LDAPアクセス用パスワード
my $ldap_server = 'example.com';                        # LDAPサーバー
my $ldap_base   = 'DC=example,DC=com';
my $ldap_bind   = "CN=$cn,OU=users,$ldap_base";
my $displayname;
my $url                 = 'http://example.com/svn/';    # SubversionリポジトリのURL

open my $file, '>', "logs/ldap_pl$rev.txt" or die $!;   # ログ記録開始

print $file '$repos: ' . "$repos\n";
print $file '$rev  : ' . "$rev\n";

my $command     = "svnlook author $repos -r $rev";
open my $dir, '-|', $command or die $!;
my $author_id;
while(my $line = <$dir>){
        chomp $line;
        $author_id = $line;
}
close($dir);

print $file '$author_id: ' . "$author_id\n";

my $ldap = Net::LDAP->new($ldap_server,);       #サーバー名

print $file "ldap: $ldap\n";

my $msg = $ldap->bind(
        $ldap_bind,
        password => $pass,
);      #ユーザ名とパスワードで認証

my $filter = "cn=$author_id";

print $file encode('cp932', decode('utf8', __LINE__ . ": " . "LDAP接続結果 = " . $msg->error . "\n"));      #ここまでのerrorログを取得
$msg = $ldap->search(
        base            => $ldap_base,
        filter          => $filter,
        attribute       => 'cn=lang-ja'
);      #search処理

for my $entry ($msg->all_entries) {
        $displayname = $entry->get_value('displayname');
}

print $file encode('cp932', decode('utf8', "$displayname\n"));
Encode::from_to($displayname, 'utf8', 'cp932');

$command        = "svn propset svn:author --revprop -r $rev $displayname($author_id) $url --username $cn --password $pass";
print $file "$command\n";
close $file;
system($command);

2010-03-04

Subversionで外部定義(svn:externals)を利用したブランチをタグリリースする。

タグを作成するときに、外部定義(svn:externals)を含んでいると、参照先が更新されたときに外部定義部分だけ最新版になっちゃったりする。

外部定義にリビジョンを明示してあげればいいんだけど、外部定義のあるところをいちいち手動で書き換えるのはめんどくさい。

サーバ側のフックスクリプト(pre-commit, post-commit等)でなんとかできないか調べてみたけど難しそうだったので、クライアント側でタグをcommitする際にsvn:externalsに参照するリビジョンを埋め込むスクリプトを組んでみた。

<使用方法>

  1. タグリリースするフォルダと同じ階層にスクリプトを置く
  2. Terminalでスクリプトを実行する
  3. タグの元になるフォルダ名を入力する
  4. タグ名を入力する
  5. コミットログを入力する

<スクリプトの内容>

  1. タグの元になるフォルダをsvn copyする
  2. コピーしたフォルダのリポジトリURLの中のbranchesをtagsに置換して、svn switchする(このときにリポジトリのHEADのリビジョン番号を取得)
  3. コピーしたフォルダに含まれるsvn:externalsを検索し、2で取得したリビジョン番号を埋め込む
  4. コピーしたフォルダをcommitする
#!/usr/local/bin/perl

use strict;
use warnings;
use Encode;

my $os  = 'linux';              # windowsで利用する場合はコメントアウトしてください。
#my $os = 'windows';    # linuxで利用する場合はコメントアウトしてください。

# 出力文字コードの選択
my $output_code;
if ($os eq 'windows'){
       $output_code    = 'cp932';
}
else {
       $output_code    = 'utf8';
}

# ブランチをコピーする
       opendir my $current_dir, './';
       print encode($output_code, decode('utf8', "フォルダ一覧:\n"));
       while (my $file = readdir($current_dir)){
               next if ($file =~ /^\./);
               if (-d $file){
                       print "$file\n";
               }
       }
       closedir($current_dir);

       # コピー元フォルダの入力
       my $src;
       while (1){
               print encode($output_code, decode('utf8', "タグリリースする元フォルダ名を入力してください:"));
               $src    = <STDIN>;
               chomp($src);
               if (-d $src){
                       last;
               }
               else {
                       print encode($output_code, decode('utf8', "指定のフォルダが存在しません。\n"));
               }
       }

       # タグ名の入力
       my $dst;
       print encode($output_code, decode('utf8', "タグ名を入力してください           :"));
       $dst    = <STDIN>;
       chomp($dst);

       # copyの前のupdate確認(とりあえずしてない)

       # svn copyの実施
       print encode($output_code, decode('utf8', "svn copyを行います。\n"));
       system("svn copy $src ./$dst");

       # 切り替え先URLの作成
       chdir($dst);
       open my $svninfo, '-|', 'svn info' or die $!;
       my $changed_url;
       while(my $line = <$svninfo>){
               chomp $line;
               if($line =~ /^URL:\s(.*)/){
                       $changed_url    = $1;
                       $changed_url    =~ s/branches/tags/;
                       print encode($output_code, decode('utf8', '次のURLにタグを作成します: ' . $changed_url . "\n"));
               }
       }
       close($svninfo);

       # svn switchの実施
       chdir("../");
       open my $svnswitch, '-|', "svn switch $changed_url $dst" or die $!;
       my $rev;
       while(my $line = <$svnswitch>){
               chomp $line;
               Encode::from_to($line, $output_code, 'utf8');
               if($line =~ /^リビジョン\s([\d]*)\sです。/){
                       $rev = $1;
               }
       }
       print encode($output_code, decode('utf8', 'svn:externalsを次のrevに固定します: ' . $rev . "\n"));
       close($svnswitch);

# externalsを書き換える
       # svn:externalsの取得
       chdir($dst);
       open my $svnpropget, '-|', 'svn propget svn:externals -R' or die $!;
       while(my $line = <$svnpropget>){
               chomp $line;
               if($line =~ /(.*)\s-\s(.*)\s(.*)/){
                       my $target      = $1;
                       my $param       = "$3 -r$rev $2";
                       # 何故か、リビジョン指定なしとリビジョン指定ありで、SRC/DSTの記述順が逆
                       # リビジョン指定なし: SRC DST
                       # リビジョン指定あり: DST -r rev SRC
#                       print '$target: ' . $target . "\n";
#                       print '$param : ' . $param  . "\n";
                       system("svn propset svn:externals \"$param\" $target");
               }
       }
       close($svnpropget);

# リポジトリにコミットする
       chdir("../");
       print encode($output_code, decode('utf8', "コミットログを入力してください: "));
       my $commit_log  = <STDIN>;
       chomp($commit_log);
       system("svn commit $dst -m $commit_log");

__END__

以下、いろいろ参考にさせてもらったところ。(結果的に関係なかったものも含む)

続きを読む

2010-03-03

PARでハマるのこと

pp -o helloworld.exe helloworld.pl

とすると、こんなエラーが発生していた。

Can't locate loadable object for module IO in @INC (@INC contains: CODE(0x10b483
0) .) at C:/xampp/perl/lib/IO/Handle.pm line 262
Compilation failed in require at C:/xampp/perl/lib/IO/Handle.pm line 262.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/Handle.pm line 262.
Compilation failed in require at C:/xampp/perl/lib/IO/Seekable.pm line 101.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/Seekable.pm line 101.
Compilation failed in require at C:/xampp/perl/lib/IO/File.pm line 133.
BEGIN failed--compilation aborted at C:/xampp/perl/lib/IO/File.pm line 133.
Compilation failed in require at -e line 313.

環境変数Perl5libがセットされていなかった。

Perl5lib=C:\xampp\perl\lib

これをセットしたら解決した。

以下、いろいろ参考にさせてもらったところ。(結果的に関係なかったものも含む)

続きを読む