2010年11月28日日曜日

Spring SecurityでDB接続してユーザ認証してみる

Springの認証機構Spring SecurityでのDBと繋いだログイン処理。

・環境
Spring 3.0.5
Roo 1.1.0

基本となる各種設定をSpring Rooでセットアップします。

roo> security setup

Managed ROOT\pom.xml [Adding property 'spring-security.version' to '3.0.2.RELEASE']
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-core:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-config:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-web:${spring-security.version}]
Managed ROOT\pom.xml [Added dependency org.springframework.security:spring-security-taglibs:${spring-security.version}]
Created SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
Created SRC_MAIN_WEBAPP\WEB-INF\views\login.jspx
Managed SRC_MAIN_WEBAPP\WEB-INF\views\views.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\web.xml
Managed SRC_MAIN_WEBAPP\WEB-INF\spring\webmvc-config.xml

Spring RooによってSpring Securityアドオンがインストールされ、web.xmlにプロキシが登録されたり、各種viewが作成されます。
(実行時のSpring Securityバージョンは3.0.2)

認証機構の定義ファイルとして
SRC_MAIN_RESOURCES\META-INF\spring\applicationContext-security.xml
が生成されます。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <intercept-url pattern="/choices/**" access="hasRole('ROLE_ADMIN')"/>
        <intercept-url pattern="/member/**" access="isAuthenticated()" />
        <intercept-url pattern="/resources/**" access="permitAll" />
        <intercept-url pattern="/**" access="permitAll" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <password-encoder hash="sha-256"/>
         <user-service>
             <user name="admin" password="8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" authorities="ROLE_ADMIN"/>
          <user name="user" password="04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" authorities="ROLE_USER"/>
      </user-service>
     </authentication-provider>
 </authentication-manager>

</beans:beans>

デフォルトのこの設定ファイルで、
"/choices/**"のURLは'ROLE_ADMIN'なロールを持っていること、
"/member/**"のURLは認証されていること、
その他のアクセスは無制限に許可、
のインターセプトがかかります。

また、User/Passがadmin/adminの場合はROLE_ADMIN、user/userの場合はROLE_USERな権限設定がXMLに直接記述されています。

これをDBに問い合わせ、ユーザ認証を行うように変更します。

<?xml version="1.0" encoding="UTF-8"?>

<beans:beans xmlns="http://www.springframework.org/schema/security"
    xmlns:beans="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.0.xsd">

 <!-- HTTP security configurations -->
    <http auto-config="true" use-expressions="true">
     <form-login login-processing-url="/resources/j_spring_security_check" login-page="/login" authentication-failure-url="/login?login_error=t"/>
        <logout logout-url="/resources/j_spring_security_logout"/>
        
        <!-- Configure these elements to secure URIs in your application -->
        <!-- cssやimgなどWEBリソースは無制限 -->
        <intercept-url pattern="/resources/**" access="permitAll" />
        <!-- ログイン処理は無制限 -->
        <intercept-url pattern="/login/**" access="permitAll" />
        <!-- それ以外はadmin権限 -->
        <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
    </http>

 <!-- Configure Authentication mechanism -->
    <authentication-manager alias="authenticationManager">
     <!-- SHA-256 values can be produced using 'echo -n your_desired_password | sha256sum' (using normal *nix environments) -->
     <authentication-provider>
      <!-- sha-256でパスワードをエンコーディングする -->
      <!-- "admin"文字列のハッシュ => "8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918" -->
      <!-- "user"文字列のハッシュ => "04f8996da763b7a969b1028ee3007569eaf3a635486ddab211d512c85b9df8fb" -->
      <password-encoder hash="sha-256"/>
      <!-- データソース指定。SQL指定。 -->
      <jdbc-user-service data-source-ref="dataSource"
      users-by-username-query="select users_id as username, password as password, true as enabled from users where users_id=?"
      authorities-by-username-query="select users_id as username, 'ROLE_ADMIN' as authority from users where users_id=?"/>
     </authentication-provider>
 </authentication-manager>
</beans:beans>

以下で記述されているスキーマを定義することが好ましいですが、上記の設定ではusersテーブル一つで処理を行っています。
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

usersテーブルにはusers_id、passwordの2つのフィールドが定義されているものとします。

users-by-username-query設定で以下のクエリを発行し、認証を許可するユーザを取得します。

select
    users_id as username,
    password as password,
    true     as enabled
from
    users
where
    users_id=?

authorities-by-username-query設定で実際の認証設定を取得する以下のクエリを発行します。
ユーザが存在し、users-by-username-queryでのユーザ取得が行えれば全てのリソースに接続可能とするため、'ROLE_ADMIN'な固定値をauthorityとして返却します。

select
    users_id     as username,
    'ROLE_ADMIN' as authority
from
    users
where
    users_id=?

usersテーブルに以下のデータを登録すればhoge/adminでログイン可能となります。
users_id:hoge
password:8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918

参考資料)

・Spring Securityの機能について
http://ameblo.jp/spring-beginner/entry-10234230466.html

・実際に認証と認可をWEBにつけるには?
http://ameblo.jp/spring-beginner/entry-10236817063.html

・Spring by Example - Simple Spring Security Webapp
http://www.springbyexample.org/examples/simple-spring-security-webapp-spring-config.html

・5 Minute Guide to Spring Security
http://www.mularien.com/blog/2008/07/07/5-minute-guide-to-spring-security/

・Using Spring-Security Database in Spring-Roo
http://roosbertl.blogspot.com/2010/06/using-spring-security-database-in.html

・Security Database Schema
http://static.springsource.org/spring-security/site/docs/2.0.x/reference/appendix-schema.html

・Security Namespace Configuration
http://static.springsource.org/spring-security/site/docs/3.1.x/reference/ns-config.html#ns-auth-manager

・概説 Springプロダクト(3) - Spring Securityでユーザ認証/アクセス制御
http://journal.mycom.co.jp/articles/2010/03/25/spring3/index.html

・Spring Roo日本語リファレンス - 2.10 セキュアなアプリケーション
http://www.spring-roo.com/home/210

2010年11月10日水曜日

EclipseプラグインでJetty連携

開発用のAPコンテナをJettyにしよう作業してたところ、EclipseからJettyを起動させるプラグインを発見。

http://docs.codehaus.org/display/JETTY/Web+Tooling+Support


これはいいや、と書かれている通りに構築していたのですが、以下のエラーが出て起動できず。

java.lang.NoClassDefFoundError: org/mortbay/start/Main
Caused by: java.lang.ClassNotFoundException: org.mortbay.start.Main
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Exception in thread "main" 

別途アプリをデプロイすればいいだけなのですが、開発資材を毎度毎度操作するのは面倒過ぎる。。

何だかんだで5時間近く色々と試してみたところ、当該プラグインがJetty7には対応していない、というオチ。

こことか、ここにその旨ありました。

Jetty6系だとちゃんと動きます。

2010年11月9日火曜日

Eclipseで比較エディタが開けなくなった。

Eclipse Version: 3.6.1

Updateかけたら比較エディタが開けなくなった。

こんなエラーが出る。

Could not open the editor: An unexpected exception was thrown.

java.lang.NullPointerException
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.verifyValidity(FormatPreferencesSupport.java:100)
 at org.eclipse.php.internal.core.format.FormatPreferencesSupport.getIndentationChar(FormatPreferencesSupport.java:89)
 at org.eclipse.php.internal.ui.editor.configuration.PHPStructuredTextViewerConfiguration.getIndentPrefixes(PHPStructuredTextViewerConfiguration.java:448)
 at org.eclipse.wst.sse.ui.internal.StructuredTextViewer.configure(StructuredTextViewer.java:311)
 at org.eclipse.php.internal.ui.compare.MergeSourceViewer.configure(MergeSourceViewer.java:542)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.internalSetDocument(TextMergeViewer.java:546)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer$ContributorInfo.setDocument(TextMergeViewer.java:434)
 at org.eclipse.php.internal.ui.compare.TextMergeViewer.updateContent(TextMergeViewer.java:2569)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.internalRefresh(ContentMergeViewer.java:814)
 at org.eclipse.php.internal.ui.compare.ContentMergeViewer.inputChanged(ContentMergeViewer.java:704)
 at org.eclipse.jface.viewers.ContentViewer.setInput(ContentViewer.java:274)
 at org.eclipse.compare.CompareViewerSwitchingPane.setInput(CompareViewerSwitchingPane.java:276)
 at org.eclipse.compare.internal.CompareContentViewerSwitchingPane.setInput(CompareContentViewerSwitchingPane.java:158)
 at org.eclipse.compare.CompareEditorInput.internalSetContentPaneInput(CompareEditorInput.java:844)
 at org.eclipse.compare.CompareEditorInput.access$8(CompareEditorInput.java:842)
 at org.eclipse.compare.CompareEditorInput$11.run(CompareEditorInput.java:778)
 at org.eclipse.swt.custom.BusyIndicator.showWhile(BusyIndicator.java:70)
 at org.eclipse.compare.CompareEditorInput.feed1(CompareEditorInput.java:772)
 at org.eclipse.compare.CompareEditorInput.feedInput(CompareEditorInput.java:750)
 at org.eclipse.compare.CompareEditorInput.createContents(CompareEditorInput.java:554)
 at org.eclipse.compare.internal.CompareEditor.createCompareControl(CompareEditor.java:456)
 at org.eclipse.compare.internal.CompareEditor.createPartControl(CompareEditor.java:414)
 at org.eclipse.ui.internal.EditorReference.createPartHelper(EditorReference.java:670)
 at org.eclipse.ui.internal.EditorReference.createPart(EditorReference.java:465)

数日無視してたんですが、、地味に困るぜ、、ということで調べてみた。

何でも知ってるGoogle先生に聞けばすぐに判明。
https://bugs.eclipse.org/bugs/show_bug.cgi?id=326227
PDTのバグらしい。

Updateサイトを登録してHelp>Check for Updates
http://download.eclipse.org/tools/pdt/updates/2.2/milestones

PDT2.2が落ちてくるので更新すればOK。

2010年10月20日水曜日

PHPExcel1.7.4の不具合?について

以前PHPExcelのメモリ馬鹿食いについてで触れていたコードがPHPExcel1.7.4では正常に動作しないことが判明しました。

ワークシートオブジェクトのclone処理で、セルへの参照が1.7.4では上手くハンドリングされていないようで、書式が設定されたセルへ値を書き込むと、別のシートの同じセルにも値が書き込まれることがあります。

再現する条件を完全には洗い出せていませんが、書式が何も設定されていないプレーンなセルに対する書き込みは問題ありませんが、以下のような条件で別のシートの同じセルにも値が書き込まれることがあります。

・書き込むセルに既に値が入力されている
・書き込むセルがセル結合されている

他にもワークシート名の変更もちょっと動作が怪しいことがあります。

PHPExcelの1.7.4には不具合があるようです。
というよりも根本的な不具合があるのは1.7.3系で、PHPExcelのメモリ馬鹿食いについてで触れていたメモリリークっぽい現象を修正しようとして失敗しているようです。

セルのデータをキャッシュ(デフォルトではメモリ上に展開?)し、それを利用してセルのコピーやclone処理を行っているようですが、1.7.4系ではデータキャッシュのライブラリが格納されているClasses/PHPExcel/CachedObjectStorage配下が大幅に修正されています。

1.7.4は使用せずに1.7.3cに巻き戻した方が良さそうです。

1.7.3cではPHPExcel_CachedObjectStorage_Memcache.phpに明らかなバグ(単純ミス)があって気持ち悪いので、その箇所のみパッチ当てて使ってます。
ただし、このクラスはPHPExcel_Settingsを通して明示的にmemcachedをキャッシュオブジェクトとして指定しないと使われないようなので、デフォルト設定では実害はないようです。

以下、パッチ。

189行目
- if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback')) {
+ if (!$this->_memcache->addServer($memcacheServer, $memcachePort, false, 50, 5, 5, true, array($this, 'failureCallback'))) {

200行目
- throw new Exception('memcache '.$host.':'.$port' failed');
+ throw new Exception('memcache '.$host.':'.$port.' failed');

それから、PHPExcelのメモリリークを調べていた際に参考にさせて頂いたサイト【PHP】PHPExcelがループ内でメモリを使いすぎるで「さらに注意点としては、1.7.3cはaddExternalSheetにバグがあるので、最新のソースをダウンロードした方がいい。」との記述がありますが、これもPHPExcel.phpに以下のパッチを当てれば良さそう。

437行目
- $cell = $sheet->getCell($cellID);
+ $cell = $pSheet->getCell($cellID);

パッチを当てた1.7.3cで当分しのいで、1.7.4の修正版が出るのを待つ作戦をおすすめ。

2010年10月10日日曜日

PHPExcelでセルの背景色指定

背景色の付け方自体は以下の参考リンクから。

PHPExcel-セルのスタイル
PHPExcel(リファレンス1)
PHPExcel セル背景色 & 行と列からアドレスに変換

上記、すごく助かったんですが、実際にどんな値設定すれば何色になるのかリファレンスは見つからず。
PHPExcel_Style_Colorにいくつか有用な定義がされていますが、今一つ何色になるのか分かりにくい。

    const COLOR_BLACK                        = 'FF000000';
    const COLOR_WHITE                        = 'FFFFFFFF';
    const COLOR_RED                            = 'FFFF0000';
    const COLOR_DARKRED                        = 'FF800000';
    const COLOR_BLUE                        = 'FF0000FF';
    const COLOR_DARKBLUE                    = 'FF000080';
    const COLOR_GREEN                        = 'FF00FF00';
    const COLOR_DARKGREEN                    = 'FF008000';
    const COLOR_YELLOW                        = 'FFFFFF00';
    const COLOR_DARKYELLOW                    = 'FF808000';

    public static function indexedColor($pIndex) {
        // Clean parameter
        $pIndex = intval($pIndex);

        // Indexed colors
        if (is_null(self::$_indexedColors)) {
            self::$_indexedColors = array();
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00000000';
            self::$_indexedColors[] = '00FFFFFF';
            self::$_indexedColors[] = '00FF0000';
            self::$_indexedColors[] = '0000FF00';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008000';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00808000';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '00C0C0C0';
            self::$_indexedColors[] = '00808080';
            self::$_indexedColors[] = '009999FF';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00FFFFCC';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00660066';
            self::$_indexedColors[] = '00FF8080';
            self::$_indexedColors[] = '000066CC';
            self::$_indexedColors[] = '00CCCCFF';
            self::$_indexedColors[] = '00000080';
            self::$_indexedColors[] = '00FF00FF';
            self::$_indexedColors[] = '00FFFF00';
            self::$_indexedColors[] = '0000FFFF';
            self::$_indexedColors[] = '00800080';
            self::$_indexedColors[] = '00800000';
            self::$_indexedColors[] = '00008080';
            self::$_indexedColors[] = '000000FF';
            self::$_indexedColors[] = '0000CCFF';
            self::$_indexedColors[] = '00CCFFFF';
            self::$_indexedColors[] = '00CCFFCC';
            self::$_indexedColors[] = '00FFFF99';
            self::$_indexedColors[] = '0099CCFF';
            self::$_indexedColors[] = '00FF99CC';
            self::$_indexedColors[] = '00CC99FF';
            self::$_indexedColors[] = '00FFCC99';
            self::$_indexedColors[] = '003366FF';
            self::$_indexedColors[] = '0033CCCC';
            self::$_indexedColors[] = '0099CC00';
            self::$_indexedColors[] = '00FFCC00';
            self::$_indexedColors[] = '00FF9900';
            self::$_indexedColors[] = '00FF6600';
            self::$_indexedColors[] = '00666699';
            self::$_indexedColors[] = '00969696';
            self::$_indexedColors[] = '00003366';
            self::$_indexedColors[] = '00339966';
            self::$_indexedColors[] = '00003300';
            self::$_indexedColors[] = '00333300';
            self::$_indexedColors[] = '00993300';
            self::$_indexedColors[] = '00993366';
            self::$_indexedColors[] = '00333399';
            self::$_indexedColors[] = '00333333';
        }

        if (array_key_exists($pIndex, self::$_indexedColors)) {
            return new PHPExcel_Style_Color(self::$_indexedColors[$pIndex]);
        }

        return new PHPExcel_Style_Color();
    }

ということで、Excel標準のカラーパレットに定義されている色のARGBを調べました。
カラーパレット左上から右順で、

FF000000, FF993300, FF333300, FF003300, FF003366, FF000080, FF333399, FF333333,
FF800000, FFFF6600, FF808000, FF008000, FF008080, FF0000FF, FF666699, FF808080,
FFFF0000, FFFF9900, FF99CC00, FF339966, FF33CCCC, FF3366FF, FF800080, FF969696,
FFFF00FF, FFFFCC00, FFFFFF00, FF00FF00, FF00FFFF, FF00CCFF, FF993366, FFC0C0C0,
FFFF99CC, FFFFCC99, FFFFFF99, FFCCFFCC, FFCCFFFF, FF99CCFF, FFCC99FF, FFFFFFFF,

控えておくと地味に役に立つこともあるかも。

2010年9月29日水曜日

Formを作らずにJSでPOST送信

AタグやButtonのonclickなんかを引っ掛けてPOSTしたい場合、hidden要素しか持たない隠しformを作ったりしていましたが、Javascriptだけで処理できたのでメモ。

※参考
HTMLでformを作らずにJavaScriptでPOST送信

/**
 * データをPOSTする
 * @param String アクション
 * @param Object POSTデータ連想配列
 */
function execPost(action, data) {
 // フォームの生成
 var form = document.createElement("form");
 form.setAttribute("action", action);
 form.setAttribute("method", "post");
 form.style.display = "none";
 document.body.appendChild(form);
 // パラメタの設定
 if (data !== undefined) {
  for (var paramName in data) {
   var input = document.createElement('input');
   input.setAttribute('type', 'hidden');
   input.setAttribute('name', paramName);
   input.setAttribute('value', data[paramName]);
   form.appendChild(input);
  }
 }
 // submit
 form.submit();
}

こんな感じで呼び出し。

<a onclick="execPost('/hoge', {'fuga':'fuga_val', 'piyo':'piyo_val'});return false;" href="#">POST送信</a>

2010年9月9日木曜日

MySQLのトランザクションとロック

毎回同じこと調べてる気がするので、メモ。

MySQL5.0.77、InnoDB、デフォルトのAUTOCOMMIT=1

tableテーブル
id, paramカラム


■関係ない行

・A
START TRANSACTION;
INSERT INTO `table` VALUES('1', '1');

・B
SELECT * FROM `table`
// 正常終了(取得結果0件)

・A
COMMIT;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件)


■更新中の行

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'hoge' WHERE `id` = 1;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新前の値)

・A
COMMIT;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新後の値)


■両方更新

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;
// 正常終了(更新結果1件)

・B
UPDATE `table` SET `param` = 'B' WHERE `id` = 1;
// 待ち状態

・A
COMMIT;
// BプロセスのUPDATEが終了

・B
SELECT * FROM `table`
// 正常終了(取得結果、param='B')


■ロック(READロック)

・A
LOCK TABLES `table` READ;
// 更新処理はエラー

・B
SELECT * FROM `table`
// 正常終了(取得結果1件)
// 更新系クエリは待ち

・A
// 更新処理はエラー
UNLOCK TABLES;
// BプロセスのUPDATEが終了

■ロック(WRITEロック)

・A
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;
// 正常終了(更新結果1件)

・B
SELECT * FROM `table`
// 待ち
// 更新系クエリも待ち

・A
UNLOCK TABLES;
// BプロセスのSELECTが終了
// BプロセスのUPDATEが終了


■ロックとトランザクション(ロックしてからトランザクション開始)

・A
LOCK TABLES `table` WRITE;

・B
SELECT * FROM `table`
// 待ち

・A
START TRANSACTION;
// START TRANSACTIONする前のアクティブなトランザクションは閉じられる。
// 1回目のLOCK TABLESが開放されて、BプロセスのSELECTが終了


■ロックとトランザクション(トランザクション発行してからREADロック)

・A
START TRANSACTION;
UPDATE `table` SET `param` = 'A' WHERE `id` = 1;

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新前)

・A
LOCK TABLES `table` READ;
// 開始していたトランザクションが閉じられる

・B
SELECT * FROM `table`
// 正常終了(取得結果1件、更新後)

■ロックとaoutocommit

・AUTOCOMMITを無効にしていてもUNLOCK時にはコミットされる
SET AUTOCOMMIT=0;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge5' WHERE `id` = 1;

UNLOCK TABLES;
// hoge5

・ROLLBACKすればOK
SET AUTOCOMMIT=0;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge6' WHERE `id` = 1;

ROLLBACK;
UNLOCK TABLES;
// hoge5

・でもAUTOCOMMITが有効なら意味無い
SET AUTOCOMMIT=1;
LOCK TABLES `table` WRITE;
UPDATE `table` SET `param` = 'hoge6' WHERE `id` = 1;

ROLLBACK;
UNLOCK TABLES;
// hoge6

■まとめ
・トランザクション(ロック)の発行はそれまでのロック(トランザクション)が閉じられるので1シーケンスの中にトランザクションとロックの発行を混在させないこと。
・トランザクションが終わるまで他のプロセスに値がreadされるとまずい場合、行ロックやテーブルロック。
・ロック時にはAUTOCOMMITを無効にして明示的にCOMMITすること。
・readされるとまずい場合は行ロックでは辛いので現実的にはテーブルロック

2010年7月19日月曜日

PHPExcelのメモリ馬鹿食いについて

===============
2010-10-20追記
関連POST
PHPExcel1.7.4の不具合?について
===============

PHPExcelのシート追加でメモリをガンガン消費しちゃう件。
リンク先を参考に解析。

SE奮闘記: 【PHP】PHPExcelがループ内でメモリを使いすぎる

ワーク
シート数
メモリ使用量(Byte)
改善前 改善後
1 21,233,664 21,233,664
2 22,544,384 22,544,384
3 25,165,824 23,855,104
4 30,408,704 24,903,680
5 40,632,320 25,952,256
6 60,293,120 27,262,976
7 100,139,008 28,311,552
8 180,092,928 29,360,128
9 340,000,768(推定) 30,408,704
10 659,816,448(推定) 31,457,280

改善前

$aSheet = $this->excel->getActiveSheet();
for ($i = 0; $i < count($this->data); $i++) {
    if ($i != 0) {
        $this->excel->addSheet(clone $aSheet);
    }
}

改善後

$aSheet = $this->excel->getActiveSheet();
$newSheets = array();
for ($i = 0; $i < count($this->data); $i++) {
    if ($i != 0) {
        $newSheets[] = clone $aSheet;
    }
}
foreach ($newSheets as $newSheet) {
    $this->excel->addSheet($newSheet);
}

改善前は大体次の数式のようにメモリ使用量が増えていってるっぽくてO(2^n)のオーダー(かな?)
f(n+1) = 2.5MB * 2^(n+1) + f(n)
改善後は次の数式でO(n)のオーダー。
f(n) = 1.25MB * n + 20MB

リンク先で触れられていた「シートをコピーして追加する」の処理をやめて「テンプレートからシートを流し込むとよい」という内容は確認できませんでした。
リンク先とは状況が違うとは思いますが、こちらの解析ではPHPExcel_Worksheetを__cloneする時にメモリがどんどん消費されていっているらしくて、PHPExcel_Worksheet#copy()も内部的には__cloneを呼んでいる。

だからと言って、
$this->excel->addSheet(clone $aSheet);

$newSheets[] = clone $aSheet;
になったことでそんなに劇的に変わる理由がよく分かりませんが、
$aSheet = $this->excel->getActiveSheet();
した$aSheetが指しているactiveなシートの参照先が
$this->excel->addSheet(clone $aSheet);
したことによってどうにかなってしまったんではなかろうか、と想像。

PHPExcelの実装の問題なのか、cloneする変数の参照先の親オブジェクトにループ内でメンバ変数追加するとそうなってしまうPHPの仕様なのか、何とも言えませんが、今後の為にメモ。

2010年7月9日金曜日

zend + dojo : 画像のsubmitの挙動が変だったハナシ

Zend + Dojoで画像ボタンでsubmitしようとしたんですが、サーバからの返却が何やらおかしかった。
<input type="submit">を<input type="image" src="">にしたい場面はたくさんありますが、ajaxでの接続で挙動がおかしい場面があったのでメモ。

通常のボタンでsubmitする場合

※HTML

<form action="#" id="hoge">
    <button name="btn_submit" id="btn_hoge" type="button" value="hoge">hoge</button>
</form>

// dojo.connect
<script type="text/javascript">
    // イベントハンドラの登録
    dojo.addOnLoad(function() {
        // btn_hoge要素にイベントハンドラを設定する。
        // onclickイベントを引っ掛けて、hogeフォームの内容をsubmit
        // /fuga/piyoアクションをコールして、json形式でレスポンスを受け取る。
        dojo.connect(dojo.byId("btn_hoge"), "onclick", function() {
            dojo.xhrPost({
                url: "/fuga/piyo/format/json",
                content:{"a":"b"},
                form: "hoge",
                handleAs: "json",
                load: function(data){
                    alert('OK');
                },
                error: function(error, args) {
                    alert('NG');
                }
            });
        });
    });
</script>
※Zend

/**
 * Fugaコントローラ
 */
class FugaController extends Zend_Controller_Action
{
    /**
     * piyoアクションのコンテキストとしてjsonを登録
     */
    public function init()
    {
        parent::init();
        $contextSwitch = $this->_helper->getHelper('contextSwitch');
        $contextSwitch->addActionContext('piyo', 'json')
                      ->initContext();
    }

   /**
     * Piyoアクション
     */
    public function PiyoAction()
    {
        // View変数をシリアライズしてjsonとして返却
        // {"viewMessages":"abc"}
        $this->view->message = 'abc';
    }
}

上記は、普通にajax通信できますが、以下のようにボタンを画像にすると、dojoのイベントハンドラでerror関数がコールされ、error変数として「Error: Unable to load /fuga/piyo/format/json status:0」という良く分からないエラーが返却されます。

※HTML

<form action="#" id="hoge">
    <input type="image" src="" name="btn_submit" id="btn_hoge" value="btn_submit" />
</form>

サーバでログ取ってみても、postされてたりされてなかったり、規則性なくランダムにサーバにリクエストが到達しているようでした。
error関数も複数回コールされることがあり、1回のボタン押下で何回か/puga/piyoアクションがコールされることもありました。

何でそんなことになるのか分かりませんでしたが、dojo.connectでinput属性のエレメントにハンドラを設定しているのが問題っぽい。

<form action="#" id="hoge">
</form>
<input type="image" src="" name="btn_submit" id="btn_hoge" value="btn_submit" />

こんな感じでformの外にimage要素を配置すると、サーバには1回だけ行儀よくPOSTし、返却値も期待するJSON形式で受け取れるようになります。

formの中にいる<input type="image">な要素にdojo.connectすると、dojoで接続されたイベントハンドラと、form自体のactionとの両方が実行されてしまうようです。

2010年6月27日日曜日

Dojoのdojo.requireでSSL通信したい

Dojo
※以下、Dojo1.4

Dojoでdojo.require("dijit.form.ComboBox");

なんかでモジュールをロードすると、

http://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dijit/form/ComboBox.js

こんな感じのモジュールロードのHTTP通信が発生します。

で、HTTPSで画面表示してる場合、セキュリティ警告が出る、、。

https通信でモジュールロードしたい場合のtips。

参考)Googleグループ

こんな感じでDojoをロードしている部分でモジュールのロード元を指定します。

<script src="/libs/dojo1.4/dojo.xd.js" djConfig="parseOnLoad: true, isDebug: true">

SSL対応後。

<script type='text/javascript'>
    var djConfig = {
        isDebug: true,
        parseOnLoad: true,
        modulePaths: {
            "dojo": "https://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dojo",
            "dijit": "https://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dijit",
            "dojox": "https://ajax.googleapis.com/ajax/libs/dojo/1.4.3/dojox",
            }
    };
</script>
<script src="/libs/dojo1.4/dojo.xd.js"></script>

ローカルにDojo丸ごとダウンロードしてmodulePathsでローカルのjs資材を指定しても大丈夫だと思う。

2010年6月2日水曜日

GAE環境で使えないJREパッケージ

GoogleさんのPaaS環境Google App Engienで使えないJREクラス。

ホワイトリスト
http://code.google.com/intl/ja/appengine/docs/java/jrewhitelist.html

ブラックリスト
http://d.hatena.ne.jp/uehaj/20090505/1241531042

2010/06/02現在、java.awtは使えない。
ので、帳票関係厳しい。。awt非依存のライブラリが必要。

状況証拠から、以下のようなエラーが出れば、多分使えないクラスを使ってる。
rejectとかって言われてるし。

java.lang.NoClassDefFoundError: java.awt.Point is a restricted class. Please see the Google App Engine developer's guide for more details.
at com.google.appengine.tools.development.agent.runtime.Runtime.reject(Runtime.java:51)

2010年6月1日火曜日

お名前.comのVPSにyumインストール

お名前.comのVPSを使ってみています。
何せ安いですし、サポートも丁寧で今のところお勧めです。

お名前.comのVPS、デフォルトでyumが入っていません。

yumは要るよ。。
ってことでインストール。

yumのRPM探してインストールしようと思ったのですが、色々なブログに載っているftp.riken.jpとかはリンク切れでRPM見つかりませんでした。yumのサイトも見てみたんですが、よく分からなかったのでソースからインストールしようと挑戦し、、撃沈・・・。

yum入れないと何も始まらないのに、なんか面倒なことになってきたな~、と思っていたところ、VPSのコンパネからインストールできるという記事が!!
http://www.vps-root.net/02-function/yum.html

リンク先に書いてあるとおりです。
コンパネから「セキュリティ」>「アップデート」の設定を「アップデートなし」に変更。
http://guide.onamae-server.com/vps/sys.php?c1=6&c2=26&c3=63&g=3
※この操作をするとコンパネのサポート対象外になります。

これでyum入ります。
※コンパネで変更後すぐにはyumコマンド見つかりませんでした。SSHをログアウトかSVNを再起動かなんかそんなことをしているうちに見つかるようになります。

ちなみにコンパネから手動更新してみたところ「何も更新ないですよ」って言われましたが、yum updateしてみたところ200個ぐらい落ちてきたので、yumの方がいいっぽいです。

2010年5月26日水曜日

PHP Excelで自動改行&行高さ自動調整

PHP Excelで文字の自動改行をしたい。

ここに記載されているプロパティの設定でOK。
http://phpexcel.codeplex.com/Thread/View.aspx?ThreadId=18434

$objPHPExcel->getActiveSheet()->getStyle('A1')->getAlignment()->setWrapText(true);

ただ、PHPExcelをnewするときは問題ないけれど、テンプレートファイルをロードして書き込むとテンプレートファイルの行高さがずっと保持されたままの状態になるらしい。

自動改行設定した後に行高さを調整しないとキレイに出力されない。

// テンプレートファイルの読み込み
$reader = PHPExcel_IOFactory::createReader('Excel5');
$excel = $reader->load(TEMPLATE_PATH);

// シートの設定
$excel->setActiveSheetIndex(0);
$sheet = $excel->getActiveSheet();
$sheet->getDefaultStyle()->getFont()->setName('MS Pゴシック');
$sheet->getDefaultStyle()->getFont()->setSize(9);
$sheet->setTitle('sheet name');

// 書き込み
$sheet->setCellValue('A1', 'あいうえおかきくけこさしすせそ');

// 自動改行
$sheet->getStyle('A1')->getAlignment()->setWrapText(true);

// 行高さ設定(setRowHeightにパラメタ指定しないことで自動調整)
$sheet->getRowDimension(1)->setRowHeight();

PHPでExcel出力

WEBからExcel出力させたい!
既に色々なライブラリや参考WEBサイトがありますが、備忘録として。。

※参考
http://d.hatena.ne.jp/saicologic/20080606/1212715767
http://www.syuhari.jp/blog/archives/1621
http://journal.mycom.co.jp/articles/2009/03/06/phpexcel/index.html

以下、PHP Excel + Zend Frameworkの例です。

ここここなんかを参考にPHP Excelのダウンロードと展開、パスの設定。

Excel95形式(.xls)を扱う限りはphp_zipは必要なさそうです。

PHP Excelに身を委ねてExcelファイルを生成。
生成した一時ファイルを返却。
Content-Typeは「application/vnd.ms-excel」らしい。

include 'PHPExcel.php';
class ExcelController extends Zend_Controller_Action
{
    
    public function testAction()
    {
        // 一時ファイル
        $fileName = '/tmp/output.xls';
        
        $excel = new PHPExcel();
        // シートの設定
        $excel->setActiveSheetIndex(0);
        $sheet = $excel->getActiveSheet();
        $sheet->setTitle('sheet name');
        // セルに値を入れる
        $sheet->setCellValue('A1', 'あいうえお');
        // Excel95 形式で出力
        $writer = PHPExcel_IOFactory::createWriter($excel, 'Excel5');
        $writer->save($fileName); // ホントはファイル出力せずにバイナリで返して欲しい
        
        // ダウンロード
        $this->getResponse()
             ->setHeader('Content-Type', 'application/vnd.ms-excel')
             ->setHeader('Content-Disposition', 'attachment; filename="test.xls"')
             ->appendBody(file_get_contents($fileName))
             ->sendResponse();
        exit;
    }
    
}

こんな感じで、
example.com/excel/test/
にアクセスでtest.xlsのダウンロード。

========================
2010/06/27追記

IEの場合、キャッシュ関係のヘッダがないとダウンロードできないらしい。
Pragmaヘッダが無いと、「このインターネットサイトを開くことができませんでした。要求されたサイトが使用できないか、見つけることができません。後でやり直してください。」とのエラー。

include 'PHPExcel.php';
class ExcelController extends Zend_Controller_Action
{
    
    public function testAction()
    {
        // 一時ファイル
        $fileName = '/tmp/output.xls';
        
        $excel = new PHPExcel();
        // シートの設定
        $excel->setActiveSheetIndex(0);
        $sheet = $excel->getActiveSheet();
        $sheet->setTitle('sheet name');
        // セルに値を入れる
        $sheet->setCellValue('A1', 'あいうえお');
        // Excel95 形式で出力
        $writer = PHPExcel_IOFactory::createWriter($excel, 'Excel5');
        $writer->save($fileName); // ホントはファイル出力せずにバイナリで返して欲しい
        
        // ダウンロード
        $this->getResponse()
             ->setHeader('Content-Type', 'application/vnd.ms-excel')
             ->setHeader('Content-Disposition', 'attachment; filename="test.xls"')
             ->setHeader('Cache-Control', 'no-cache') // IE
             ->setHeader('Pragma', 'no-cache')        // IE
             ->appendBody(file_get_contents($fileName))
             ->sendResponse();
        exit;
    }
    
}

========================
2010/07/10追記

atachementのfilenameはそのまま送出するとブラウザによって文字化けする。

※参考
http://oku.edu.mie-u.ac.jp/~okumura/php/filename.php
http://d.hatena.ne.jp/guangda/20100106/1262762061
http://d.hatena.ne.jp/guccyon/20080530/p1

とりあえず、お手軽安全なのはSJISに変換か。。
Win環境以外は知らないことにしよう。

// ダウンロード
$fileName = mb_convert_encoding($fileName, 'SJIS');
$this->getResponse()
     ->setHeader('Content-Type', 'application/vnd.ms-excel')
     ->setHeader('Content-Disposition', 'attachment; filename="' . $fileName . '"')
     ->setHeader('Cache-Control', 'no-cache') // IE
     ->setHeader('Pragma', 'no-cache')        // IE
     ->appendBody(file_get_contents($fileName))
     ->sendResponse();

2010年5月15日土曜日

javascriptのform.submitが効かないとき

form.submitアクションがどのブラウザでも効かないなんて・・・。
なんて悩んでいたところ全く同じ事象を挙げて下さっている方がいたのでリンク。
nameには気をつけろ、と。。

form.submit()ができない!?

2010年5月6日木曜日

お手軽にlightboxっぽくformをポップアップ表示してみる

エヴァンゲリオンが出てから「セカイ系」というジャンルが確立したように、lightboxが出てから「lightbox系」というポップアップ表示スクリプトのジャンルが確立しつつあるようです。

google先生に「lightbox系」でお伺いを立ててみれば、色々な方が画像ポップアップのスクリプトをまとめて下さっているのがよく分かります。

WEBにちょっとした彩を与えてくれるlightbox。
画像のポップアップだけじゃなくてformもポップアップで出したいよね!

本家lightboxはもちろん、lytebox、lightwindow、SimpleModalなどのlightbox系ライブラリでformのポップアップ表示が可能なようです。

実現方法は色々あるのですが、うぁ!すっげー!!みたいなエフェクトは要らないし、ライブラリのAPI調べるのも面倒だなー、ってな時にお勧めのformのポップアップスクリプトを見付けましたのでご紹介。

Form in a Lightbox

ここに書かれている通りなのですが、少し補足。

このライブラリはHTMLの中の<div id="box">なエレメントをポップアップっぽく表示しているだけです。
ですので、formの入力値を親ウインドウに渡したい時に親ウインドウのハンドルを取ってきたりする必要はなく、そのままdocument.getElementByIdなんかで親ウインドウやポップアップウインドウの要素も取得できます。

デモソースが格納されているアーカイブがありますので、ダウンロードして試してみます。
http://www.xul.fr/javascript/lightbox-form.zip

展開してブラウザで表示してみれば、クライアントだけで完結していることが分かります。

少し手を入れて、ポップアップformの属性を親ウインドウで取得してみます。

ダウンロードした
lightbox-form/lightbox-form-demo.html
を編集します。

親ウインドウの<div id="hoge">hoge</div>要素にポップアップウインドウで選択した都市名を表示させてみます。

<script type="text/javascript">
function hookSelect() {
  var select = document.getElementById("select_city");
  var text = select.options[select.selectedIndex].text;
  var div = document.getElementById("hoge");
  div.innerHTML = text;
}
</script>

こんな感じのスクリプトをonchangeで引っ掛けます。

<select name="select" id="select_city" onchange="hookSelect();">

最終的なHTMLは以下。

<html>
<head>
<title>Form in a Lightbox, the Demonstration</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<link type="text/css" rel="stylesheet" href="lightbox-form.css">
<script src="lightbox-form.js" type="text/javascript"></script>
<script type="text/javascript">
function hookSelect() {
  var select = document.getElementById("select_city");
  var text = select.options[select.selectedIndex].text;
  var div = document.getElementById("hoge");
  div.innerHTML = text;
}
</script>
</head>

<body>
<div id="hoge">hoge</div>
<h1>Form in a Lightbox, the Demonstration</h1>
<p>Demo of the use of a lightbox to show a form to fill.</p>

<br>

<div id="filter"></div>
<div id="box">
  <span id="boxtitle"></span>
  <form method="GET" action="lightbox-formulaire-test.html" target="_parent">
      
    <p>Email adress: 
      <input type="text" name="email" value="myself@somedomainname.com" maxlength="60" size="60">
      </p>
      
    <p>Male 
      <input type="radio" name="genre" value="man" checked>
      Female 
      <input type="radio" name="genre" value="woman">
    </p>
      
    <p> City of current residence 
      <select name="select" id="select_city" onchange="hookSelect();">
        <option selected>New York</option>
        <option>Chicago</option>
        <option>Miami</option>
        <option>Los Angeles</option>
        <option>Dallas</option>
      </select>
      </p>
    <p> 
      <input type="submit" name="submit">
      <input type="button" name="cancel" value="Cancel" onclick="closebox()">
    </p>
    </form>
</div>


<p> To make the form appearing, <a href="#" onclick="openbox('Title of the Form', 1)">click 
  here</a>. <br>
  In this example, a fading effect is applied, this may be disabled, to do so, 
  click on the <a href="#" onclick="openbox('Title of the Form', 0)">form with 
  no fading effect</a>.</p>


<br>

<hr>

<p>(c) 2008 <a href="http://www.xul.fr/xul.html" target="_parent">xul.fr</a></p>
</body>
</html>

これで選択した都市名が親ウインドウにも反映されるようになります。
ウインドウハンドルとか何にも要らないし、楽チン~~。

2010年5月5日水曜日

HTMLのテーブル(table)を行列固定でスクロールさせる

技術屋さんの視点からは「結構キツイな~」と思うことでも、お客様からは「えっ!?それぐらいできないの?」なんて思われることが結構あります。

その代表格はtableをExcelっぽくして欲しい、という要望ではないでしょうか。
なまじっかGooleDocsなんてものがあったりするのでExcelを日常的に使用されているお客様はWEBでもそのようなI/Fを求められます。

ExcelっぽいHTMLテーブル、、
結構キツイのよね・・・。

Flash使える場合はその方向になりますが、1画面だけ、なんて要件でしたらJavascriptで何とかしたい。

JSでテーブルをExcelっぽくするライブラリには、まとまったものではYahooUIやDojoがありますが、行列を固定したスクロールは調べた限りでは出来なさそうでした。

そんな時のJSライブラリ。


・Super Tables

http://www.matts411.com/post/super_tables/

※参考
http://c-brains.jp/blog/wsg/09/06/16-151445.php

1. CSSとJSのロード

こんな感じで。

<style type="text/css"> 
  @import "/js/Super_Tables_0_30beta_compressed/stylesheets/superTables_compressed.css";
</style> 
<script src="/js/Super_Tables_0_30beta_compressed/javascripts/superTables_compressed.js" type="text/javascript"></script>

2. テーブル全体のスタイル

こんな感じで。

<style type="text/css">
.fakeContainer {
    margin: 0 0 20px;
    border: none;
    width: 400px;
    height: 100px;
    overflow: hidden;
}
</style>

3. テーブル作成

<div class="fakeContainer">
<table id="demoTableA">
  <tr>
    <th>header00</th>
    <th>header01</th>
    <th>header02</th>
    <th>header03</th>
    <th>header04</th>
    <th>header05</th>
    <th>header06</th>
    <th>header07</th>
    <th>header08</th>
    <th>header09</th>
  </tr>
  <tr>
    <th>header10</th>
    <th>header11</th>
    <th>header12</th>
    <th>header13</th>
    <th>header14</th>
    <th>header15</th>
    <th>header16</th>
    <th>header17</th>
    <th>header18</th>
    <th>header19</th>
  </tr>
  <tr>
    <td>data00</td>
    <td>data01</td>
    <td>data02</td>
    <td>data03</td>
    <td>data04</td>
    <td>data05</td>
    <td>data06</td>
    <td>data07</td>
    <td>data08</td>
    <td>data09</td>
  </tr>
  <tr>
    <td>data10</td>
    <td>data11</td>
    <td>data12</td>
    <td>data13</td>
    <td>data14</td>
    <td>data15</td>
    <td>data16</td>
    <td>data17</td>
    <td>data18</td>
    <td>data19</td>
  </tr>
  <tr>
    <td>data20</td>
    <td>data21</td>
    <td>data22</td>
    <td>data23</td>
    <td>data24</td>
    <td>data25</td>
    <td>data26</td>
    <td>data27</td>
    <td>data28</td>
    <td>data29</td>
  </tr>
  <tr>
    <td>data30</td>
    <td>data31</td>
    <td>data32</td>
    <td>data33</td>
    <td>data34</td>
    <td>data35</td>
    <td>data36</td>
    <td>data37</td>
    <td>data38</td>
    <td>data39</td>
  </tr>
</table>
</div>

4. 行列固定設定

HTMLを読み込んでからこんな感じでスクリプト実行。
(</body>直前に書けばいい)

<script type="text/javascript">
//<![CDATA[
(function () {
 new superTable("demoTableA", {
  cssSkin : "sOrange", // eg. "sDefault", "sSky", "sOrange", "sDark"
  headerRows : 1, // 固定する行数
  fixedCols : 1 // 固定する列数
  // onStart : function(), // スクリプトを実行できるらしい(試してない)
  // onFinish : function(), // スクリプトを実行できるらしい(試してない)
 });
})();
//]]>
</script>


・jquery

※元ネタ
http://fixed-header-using-jquery.blogspot.com/2009/05/scrollable-table-with-fixed-header-and.html

上述のSuper Tablesでほとんどの場合は事足りることと思われますが、テーブルにFormを入れ込まないといけなくなると途端に使えなくなります。
Super Tablesは普通のtable記述にclass打つだけで実現できるので非常に分かりやすいのですが、固定スクロールを実現するために裏でダミーのtableを複製したりしており、formをsubmitした際に新しいデータが飛んできません。(同一nameやidを持つformエレメントがSuper Tablesによって裏でいくつか複製されます。)

Super Tablesに比べてやや分かりにくくはなりますが、Pavanさんが作成してくれたリンク先のスクリプトを適用すればFormが入ったスクロールテーブルも実現可能です。
確かめてはいませんが、IE6、7、8、FF3、3.5、Chrom2で動作するとのことです。

また、リンク先の記述ではjquery-1.3.2.jsを使用していますが、jquery-1.4.2.min.jsでも動作しています。

※デモ
http://acatalept.com/common/test/fixed-table.html
※ソース
http://snipt.org/loz

2010年5月3日月曜日

Zend_Validateのメッセージを日本語化

※参考
http://framework.zend.com/manual/ja/zend.validate.introduction.html
http://framework.zend.com/manual/ja/zend.validate.messages.html

Zend Framework標準のバリデータはちゃんと使いこなせばすごく便利なのですが、デフォルトのエラーメッセージは英語になっています(当然ですが)。

実際に使用する際には日本語化する必要がありますし、また、標準のメッセージを改変しないといけないこともままあります。
このメッセージを日本語化するのがやや面倒で、以下のようなコードを記述する必要があります。

$validator = new Zend_Validate_StringLength(array('encoding' => 'utf8', 'min' => 0, 'max' => 40));
$validator->setMessage('文字列 \'%value%\'は%min%文字以上 %max%文字以下で入力してください。');

メッセージは200個ほどあるようですし、バリデータ生成するたびにメッセージ設定していればそれこそやってられません。

Zendのドキュメントにメッセージを一括で多言語化する方法が記載されていましたが、そこで紹介されているコードでは動作しない部分がありましたので以下メモ。

Zend Framework is shipped with more than 45 different validators with more than 200 failure messages. It can be a tendious task to translate all of these messages. But for your convinience Zend Framework comes with already pre-translated validation messages. You can find them within the path /resources/languages in your Zend Framework installation.
So to translate all validation messages to german for example, all you have to do is to attach a translator to Zend_Validate using these resource files.

$translator = new Zend_Translate(
'array',
'/resources/languages',
$language,
array('scan' => Zend_Locale::LOCALE_DIRECTORY)
);
Zend_Validate_Abstract::setDefaultTranslator($translator);

※参考「http://framework.zend.com/manual/ja/zend.validate.messages.html」より


1. 言語ファイルの取得

ZendFramework-1.10.4/resources/languages
に多言語化ファイルが含まれています。

ただ、Full Packageにしか含まれていないようです。
ダウンロードした資材に言語ファイルが含まれていなければ、リポジトリから取得するかFull Packageをダウンロードします。

・リポジトリ:http://framework.zend.com/code/browse/Zend_Framework
/standard/trunk/resources/以下のファイル
・パッケージ:http://framework.zend.com/download/latest


2. 言語ファイルの配置

日本語にしたい場合、必要なのは以下のファイルのみです。
ZendFramework-1.10.4/resources/languages/ja/Zend_Validate.php

任意の場所に言語ファイルを配置します。

後からメッセージも変更することでしょうし、ここではZendのインストールディレクトリではなく以下のアプリケーションディレクトリ配下に配置することとします。
/APP_ROOT/library/lang/Zend_Validate.php


3. 言語ファイルのロード

言語ファイルをロードし、Zend_Validateのメッセージを日本語化します。
これもどこで行ってもいいわけですが、Bootstrapで実行することにします。

class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
    protected function _initLang()
    {
        // 言語ファイルのロード
        $translator = new Zend_Translate(
            // Arrayアダプタを使って言語定義を取得
            'array',
            // 言語リソースのパス
            realpath(APPLICATION_PATH . '/../library/lang/Zend_Validate.php'),
            // 日本語ロケール
            'ja',
            // ファイル指定してますよー
            array('scan' => Zend_Translate::LOCALE_FILENAME)
        );
        // デフォルトのトランスレータを設定
        Zend_Validate_Abstract::setDefaultTranslator($translator);
    }
}

バージョンが違うのかもしれませんが、Zendのリファレンスに記載されている以下のコードでは動作しませんでした(Zend1.10.4)。

$translator = new Zend_Translate(
    'array',
    // インクルードパスの問題かもしれないが、フルパスでないと見れなかった。
    '/resources/languages',
    $language,
    // Zend_Locale::LOCALE_DIRECTORYなんて無い。
    // Zend_Translate::LOCALE_DIRECTORYの間違い?
    array('scan' => Zend_Locale::LOCALE_DIRECTORY)
);
Zend_Validate_Abstract::setDefaultTranslator($translator);


4. バリデートしてみる

class HogeController extends Zend_Controller_Action
{
    public function fugaAction()
    {
        $validator = new Zend_Validate_StringLength(array('min' => 0, 'max' => 3));
        $errors = array();
        if (!$validator->isValid('12345')) {
            foreach ($validator->getMessages() as $messageId  => $message) {
                $errors[] = $message;
            }
        }
        var_dump($errors);
        exit;
    }
}

・多語化前
array(1) {
  [0]=>
  string(38) "'12345' is more than 3 characters long"
}

・多語化後
array(1) {
  [0]=>
  string(39) " '12345' は 3 文字より長いです"
}

となればOK。
後は、言語ファイルを直接編集していけばメッセージの変更も一箇所で管理可能。

2010年5月2日日曜日

削除フラグって必要?

休日なので、結論の出ない軽い話題を。

DB設計の時に、場合によっては全テーブルに「delete_flag」なんていう名前でフラグを持たせることがままあります。
テーブルのデータを実際に削除せずに、「削除フラグ(delete_flag)」をONにすることで「削除したものとして扱いますよ」というフラグです。

ちなみに、実際にデータを削除することを「物理削除」、削除フラグを立てて削除したものとみなすことを「論理削除」と呼んだりもします。

この削除フラグ、特に意識せずについつい付けてしまうことが多いのですが、本当に必要なのかね・・・?というのが趣旨です。

※恐らく日本では、ある程度の規模以上のシステム開発の常識では削除フラグは絶対必要だと考えられていますので、開発リーダーに「削除フラグ要らないんじゃないっすか」みたいなことを言ってみて一蹴されても責任は負いません。


■削除フラグの根拠

削除フラグを付ける根拠としては、

1. DELETE処理よりUPDATE処理の方が早い
2. 業務データは消さない(or 履歴を残したい)

の2点が真っ先に挙げられると思います。

1.の場合、バッチ処理で後から物理削除したりもします。


■UPDATEの方が早い?

DELETE処理よりUPDATE処理の方が早いので削除フラグを持たせる、という理屈は納得できます。
ただ「そこの差」がクリティカルになるシステムは極めて限定的なものだと思われます。
DELETEとUPDATEでベンチマーク取った訳ではないですが、SQL改良したり色々キャッシュさせたり、他にするべきこと一杯あるんじゃないかな、と思っちゃったりします。


■業務データは消さない?

履歴を持ちたいから削除フラグを付ける、という理屈は納得できません。
履歴が欲しければ履歴テーブルにINSERTするべきで削除フラグで代用すべきではないと思います。
実際、今まで開発してきたシステムで履歴を参照する必要がある場合は必ず履歴テーブルを別途用意していましたので、削除フラグなんて有っても無くても一緒でした。

履歴テーブルがないテーブルの削除データを参照することは稀ですし、その必要性が出てきた段階で何だか設計間違ってるような気がします(感覚的なものですが)。

「データ復旧するかもしれないしな~」なんて思いながら削除フラグを付けることも多いのですが、データ復旧が必要な時点でそもそもシステムとして不備がある訳で・・・。

「削除したことを知る」ためのオペレーションログのような使い方も削除フラグにはあるようですが、その場合でも、正攻法はロギングの処理をちゃんと実装するべきでしょう。


■削除フラグの弊害

削除フラグがある為にいちいちSQLが面倒なことになります。
where delete_flag = '0'
って絶対書かないといけない。

SELECTの時だけでなく、UPDATEの時も毎回々々
where delete_flag = '0'

joinしてるとテーブルの数だけ削除フラグを条件に追加しないといけない。

ORマッパーなんて使ってみた日には、更にひどいことになります。
ORマッパーの機能でテーブルエンティティ間のリレーションを定義してみたものの、削除フラグだけ別に処理しないと意図するデータが取得できなくなります。
※この辺考えてくれてるORMって無いような気がします。削除フラグって日本独特なのかな・・・?

「多言語化するかも」と思ってつけておいた言語IDなんかも同じような悲劇を生みますよね?
毎回々々
where lang_id = 'ja'
みたいな・・・。
結局多言語化なんてしたことないし。

また、データを削除する際も削除フラグだけONにすれば良いわけではなく、個人情報が含まれているとデータのマスク処理も必要になります。


■削除フラグは要らない、かも

まぁ、好き好きなので結論は出ないんですが、仮に

A. DELETE処理よりUPDATE処理の方が早い、なんてことは気にしない
B. 履歴を残したい、なら履歴テーブルを作るべき

とするのであれば、削除フラグを追加すると、

1. SELECT、UPDATEのクエリ発行時に無駄な手間が毎回必ず必要になる
2. 物理削除する場合はバッチを別途作成する必要がある
3. 論理削除の際にデータにマスクをかける必要がある
4. JOINが面倒
5. 履歴として参照することはない

なんて削除フラグはわざわざシステムを複雑にするため以外の何者でもないように思えてきます。

ただ、「心持ちの問題」として稼動していた業務データを永久に葬り去る物理削除には多少のためらいもあります。
getterやsetter結構好きだったり、更新日時と作成日時をテーブルに持たせたりしますので、どちらかと言えば「削除フラグ擁護派」なのですが、やっぱり冷静に考えれば削除フラグ意味無いんじゃないかと思い出した、そんな今日この頃です。これからはためらいの心にケリをつけて、「削除フラグ撲滅派」に鞍替えします。

おしまい。

2010年4月18日日曜日

ゼロから構築するrails環境

以前のPOSTを整理しました。
CentOS にrailsインストール

CentOS5.4にrails環境を構築します。
エディタ以外のライブラリはインストールしていない空の状態からスタートです。
※BaseとEditorsパッケージのみインストール
全部rootで作業します。
root以外の場合は、必要に応じてsuして下さい。

■目標

1. Rubyのインストール
2. gemからrailsのインストール
3. プロジェクトの作成とDB設定、サーバ起動


■すること

0. 前準備

yumではruby1.8.5までしかインストールできませんが、最新のgemsではruby1.8.6以上が要求されるので、ソースからrubyをインストールします。
RPMパッケージを作成するために予めcheckinstallを導入します。

1. Rubyのインストール

rubyをソースからインストールします。

2. gemsとrailsのインストール

rubyのパッケージ管理ツールであるgemsをインストールし、gems経由でrailsをインストールします。

3. Mysqlのインストールとrails起動

Mysqlをインストールした後、railsプロジェクトを作成。
railsプロジェクト用のDB設定を行った後、webrickサーバを起動し、railsアプリケーションにアクセスします。


■前準備

gccのインストール。yumのアップデート。

$ yum install gcc
$ yum update

checkinstallのインストール。
※参考
http://herr0s.hp.infoseek.co.jp/checkinstall.html

# 依存ライブラリのインストール
$ yum install rpm-build
# checkinstallのインストール
$ cd /tmp/
$ wget http://www.asic-linux.com.mx/~izto/checkinstall/files/source/checkinstall-1.6.2.tar.gz
$ tar xzvf checkinstall-1.6.2.tar.gz
$ cd checkinstall-1.6.2
$ make
$ make install
# checkinstall自体をパッケージ管理
$ which checkinstall
$ /usr/local/sbin/checkinstall
# RPMパッケージの作成
# **********************************************************************
# 
#  Done. The new package has been saved to
# 
#  /usr/src/redhat/RPMS/x86_64/checkinstall-1.6.2-1.x86_64.rpm
#  You can install it in your system anytime using:
# 
#       rpm -i checkinstall-1.6.2-1.x86_64.rpm
# 
# **********************************************************************
$ rpm -Uvh /usr/src/redhat/RPMS/x86_64/checkinstall-1.6.2-1.x86_64.rpm


■rubyのインストール

※2010-11-24追記
この記事の本文記載の操作を行う限りにおいては問題ないと思いますが、Redmine導入時などにSSL関係のロードが上手くいかないことがあります。
最新のパッチレベルのソースを利用されることをお勧めします。
現時点での1.8.7系の最新は「ruby-1.8.7-p302」です。

# 依存ライブラリのインストール
$ yum install zlib-devel
$ yum install openssl-devel
# rubyインストール
$ cd /tmp/
$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p249.tar.gz
$ tar xzvf ruby-1.8.7-p249.tar.gz
$ cd ruby-1.8.7-p249
$ ./configure --prefix=/usr
$ make
$ checkinstall --fstrans=no
# **********************************************************************
# 
#  Done. The new package has been saved to
# 
#  /usr/src/redhat/RPMS/x86_64/ruby-1.8.7-p249-1.x86_64.rpm
#  You can install it in your system anytime using:
# 
#       rpm -i ruby-1.8.7-p249-1.x86_64.rpm
# 
# **********************************************************************
$ rpm -Uvh /usr/src/redhat/RPMS/x86_64/ruby-1.8.7-p249-1.x86_64.rpm
$ ruby -v
# ruby 1.8.7


■gemsとrailsのインストール

gemsもRPM化したかったんですが、エラーが出てパッケージができません。
※参考
http://moimoitei.blogspot.com/2008/08/centos-52-ruby.html
http://blog.livedoor.jp/tsuchy_h231/archives/50486570.html
http://d.hatena.ne.jp/varlog/20090307/1236380738

checkinstall -R "ruby ./setup.rb"
とすると、
/var/tmp/tmp.pptES10730/installscript.sh: line 4: ruby setup.rb: command not found
なんてエラーが出て進まなかったので、保留。
単にsetup.rbを実行することに。

$ cd /tmp/
$ wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.6.tgz
$ tar xzvf rubygems-1.3.6.tgz
$ cd rubygems-1.3.6
$ ruby setup.rb
$ gem -v
# 1.3.6

# railsのインストール
$ gem install rails


■Mysqlのインストールとrails起動

tmpディレクトリにrailsプロジェクトを作成し、DBと接続、サーバ起動まで。

Mysql関連初期設定

# DBとライブラリのインストール
$ yum install mysql-devel mysql mysql-server
$ gem install mysql
# 初期設定
$ mysql_install_db

railsプロジェクト作成

$ cd /tmp/
# mysql指定でdemoプロジェクト作成
$ rails -d mysql demo

rails -d mysql demo
コマンドを実行すると、./demoディレクトリが作成され、
localhostのMysqlのdemo_developmentデータベースをrootユーザ、空パスワードでアクセスするデフォルトのDB定義ファイルが作成されます。
必要に応じてDB定義ファイルを変更して下さい。

DB作成やリモートアクセス許可

# rails用DB作成
$ mysql -u root -p
mysql> create database demo_development;
mysql> exit
# リモートアクセスの許可
$ service iptables stop
# 必要に応じてrailsのDB設定を変更
# vi /tmp/demo/config/database.yml
# webrickサーバ起動
$ ruby demo/script/server

http://host:3000/
にアクセスして起動確認。
About your application’s environment
リンクをクリックしてみて情報が表示されていれば、DBとも接続できている。

2010年4月7日水曜日

ERP5をインストールしてみる、の巻き。

知ってる人は知っているフランス生まれのオープンソースのERPパッケージ「ERP5」。
http://www.erp5.org/

開発者(というかCTO)にGRUBの奥地さんがいらっしゃる(いらっしゃった?)ことでも有名。

日本でも事業展開されているようです。
http://www.nexedi.co.jp/

ぶーとろーだーと言えばGRUB。
GRUBと言えばぶーとろーだー。

何だかすごそうっぽいことだけは分かるんですが、ブートローダーが具体的に何をしているのかもよく知りません。
数年前にノートPCにfedoraとwindowsのデュアル環境を構築した際にGRUBさんにはお世話になった記憶がありますが、今の自宅サーバのブートローダーが何なのかも知らない。
そんな私ですが、奥地さんにはお会いしたいしてみたいなー。

やや脱線気味ですが、本題のERP5のインストール。

ERP5は(ほぼ)pythonで書かれており、ZepoというWEBサーバが必要になるようです。
またMandrivaというLinuxディストリビューション上で構築するのが正しいスタイルであるようです。

でもMandrivaLinuxって聞いたこともなかったですし、馴染みのある、CentOSで構築したいじゃない?
(動作確認のために構築したかっただけだし・・・)

MandrivaもRedHatベースのようなんで何とかなるかな、と思って作業し出したのですが、結論から言うと素直にMandrivaにするべきです。
CentOSでの構築方法は全然見つからない。日本語情報はもちろん、英語の情報も全然見つからない。
ERP5のオフィシャルページにもMandriva前提のurpmiコマンド(yumっぽいやつらしい)なんかで説明してあって全然参考にならない。

オフィシャルページには何通りかの構築方法が紹介されていますが、CentOSユーザにはソースをコンパイルする選択肢しかありません。
http://www.erp5.org/Download

コンパイルと言っても、パッケージやインストーラなどが提供されているので慣れれば簡単に構築できると思いますが、コマンドを実行する階層がずれれば続行できなくなったりするなど、なかなか手強いです。


■まず、インストーラを使う方法。

CentOSでは上手く行かない部分があって、結局諦めましたが、ディストリビューションによってはインストーラの使用がもっとも楽だと思います。

http://www.erp5.org/DownloadERP5Installer
ま、ここに書かれている通りなんですが、glibcのバージョンに注意して構築を始めます。

$ tar xzvf erp5-installer
$ cd erp5installer/
$ ./install.sh

 ----------------------------------
|  Checking for required packages  |
 ----------------------------------
Checking for ImageMagick...........Not Installed
Checking for pdftk.................Not Installed
Checking for pdftohtml.............Not Installed
Checking for xpdf..................Not Installed
Checking for MySQL server..........OK

色々と必要なライブラリがあります。
地道に入れていきます。

ImageMagick
http://www.imagemagick.org/script/index.php

pdftk
http://www.accesspdf.com/pdftk/

pdftkはCentOS標準のyumリポジトリにないのでrpm登録。
※参考
http://d.hatena.ne.jp/scientre/20090523/pdftk
※アーキテクチャに注意すること。リンク先はi386、x86は下記rpm。

pdftohtml
http://pdftohtml.sourceforge.net/

$ yum install ImageMagick
$ rpm -Uhv http://apt.sw.be/redhat/el5/en/x86_64/rpmforge/RPMS/rpmforge-release-0.3.6-1.el5.rf.x86_64.rpm
$ yum install pdftk
$ yum install pdftohtml
#poppler-utilsがインストールされる

ここまでは良かったんですが、この後のxpdfがCentOSにはキツイです。

xpdfはforkしてpopplerというライブラリになったようなのですが、popplerをインストールしてもERP5のインストーラは動いてくれません。
上記pdftohtmlのインストールでpopplerはインストールされているのですが、それでも駄目だと言われます。

yumでxpdf探してもCentOSにはありません。

CentOS用にFedoraのライブラリを提供してくれているサイト(http://centos.karan.org/)のパッケージも見てみましたがxpdfは無いようです(2010/04/07時点)。

CentOS用のxpdfインストールパッケージを書いてくれている人も居るようですが、インストールディレクトリのパスが通らないようで動きませんでした。
http://computingfunnyfacts.blogspot.com/2008/11/xpdf-in-centos.html

仕方がないのでソースからインストールしようとしましたが、Xが必要だとか言われてコンパイルできませんでした。
結局諦めたのですが、途中までの手順です。

※参考
http://install.pocari.org/xpdf-3.html

依存ライブラリインストール
$ yum install freetype
$ yum install t1lib
リンク先のようにyumではなくソースからインストールすると通るのかもしれない。

ソースの取得とパッチの適用からインストールまで

$ wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02.tar.gz
$ wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02pl1.patch
$ wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02pl2.patch
$ wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02pl3.patch
$ wget ftp://ftp.foolabs.com/pub/xpdf/xpdf-3.02pl4.patch
$ tar xzf xpdf-3.02.tar.gz
$ cd xpdf-3.02
$ patch -p1 < ../xpdf-3.02pl1.patch
$ patch -p1 < ../xpdf-3.02pl2.patch
$ patch -p1 < ../xpdf-3.02pl3.patch
$ patch -p1 < ../xpdf-3.02pl4.patch
この後、いつものように./configureするのですが、オプションの設定がよく分かりませんでした。
※freetype-develとt1lib-develを入れればよかったのかも。

オプション指定せずに./configureしてみたところ、X関係が必要だとかで進みませんでした。

$ yum groupinstall "X Window System"

してみても駄目。結局諦めちゃいました。


■Buildoutを利用する方法

今後、現行のRPMでのインストールなどもbuildoutというインストールシステムへ移行するようです。
http://www.erp5.org/HowToUseBuildout

色々と試してみましたが最終的にはこの方法でインストールしました。
ただ、この方法でZepoサーバも同時にインストールされるはずなんですが、これまでの格闘の際にインストールしていた別のZepoサーバのファイルを参照しにいったりするような構成が出来上がってしまっていまして、ちゃんと切り分けて整理できていない部分もあります。
お約束ですが、試してみる際は、無保証自己責任でお願いします。

上記リンクに「Dependencies:」との記載がありますが、CentOSでは全然上手くいきません。
トライアンドエラーの結果、以下のライブラリをインストールすればコンパイルできるようになりました。
※多分必要ないのもある。もしかするとメモし忘れてるのもあるかもしれない。

$ yum install python-devel
$ yum install glib-devel
$ yum install glib2-devel
$ yum install glibc-devel
$ yum install cyrus-sasl-devel
$ yum install cyrus-sasl-devel
$ yum install openldap-devel
$ yum install libxml2-devel
$ yum install libxslt-devel
$ yum install subversion-devel
$ yum install gcc-c++
$ yum install neon-devel

buidoutパッケージを任意の場所に配置しますが、配置した場所がZepoサーバのインストール先になるので適当な場所にして下さい。

またここから先はroot以外での作業をお勧めします。
最後、サーバを立ち上げる時にrootでは実行できません。
一般ユーザでサーバ起動コマンドを実行しないとエラーになります。
rootでインストールしてしまうと、ファイルのパーミッション変更などが大変です。

$ svn co https://svn.erp5.org/repos/public/experimental/erp5.buildout/
$ cd erp5.buildout/

#MySQLの接続情報を設定ファイルに記述

$ vi buildout.cfg

[buildout]
extends = profiles/development.cfg

[create_erp5_site]
# modify this to reflect your local mysql configuration
# Format:
#     database[@host[:port]] [user [password [unix_socket]]]
#   e.g "erp5db erp5user somepassword" or "erp5db erp5user"
- erp5_sql_connection_string = erp5 erp5user
+ erp5_sql_connection_string = erp5 erp5 erp5
#「erp5_sql_connection_string = 」の後に、DB名、ユーザ名、パスワード
# database[@host[:port]] [user [password [unix_socket]]]

$ python2.4 bootstrap/bootstrap.py
$ python2.4 ./bin/buildout
#cd ./bin
#python2.4 ./buidout
#とかすると動作しない

これでがちゃがちゃコンパイルしてインストールが始まります。
WARNING出し続けながら数十分掛かってインストールが完了します。

インストールが完了すれば、パッケージ直下から以下のコマンドでサーバが起動。

$ ./bin/zopectl fg

上手く行っていれば
http://host:18080
でZepoにアクセスできるはずです。

2010年3月24日水曜日

Aptana、RadRailsインストールでrackが入らない。

WinXP環境のeclipseでAptanaのRadRailsプラグインをインストール後、Auto-install gemsでエラー。
rack requires test-spec (>= 0)
とのことで、rackがインストールできず、rackがなければactionpackもactionmailerもインストールできず、結局railsが入らない、と。

何となく困った時のアップデート。

# gem update

更新無いって。
コマンドプロンプトでtest-specを入れてみる。

# gem install test-spec
ERROR:  could not find gem test-spec locally or in a repository

リポジトリに無いだと・・・。
gemのリポジトリってどうやって操作するんだろ?
ググってみたものの分からず。

gem入れ替えようと思ってバージョン確認してみたら、何か古い。

# gem -v
0.9.4

最新は1.3.6なので、gem自体を更新。

# gem update --system

OKっぽい。
これでもう一度更新かけてみると、山ほど更新が落ちてくる。

# gem update

'nmake'コマンドがないよ、とか言われるけど、出来てるっぽい。
update完了後、radrailsのAuto-installを実行してみたら通りました。
Auto-install後、もう一度updateかけた方がいい。

2010年3月23日火曜日

CentOSにrailsインストール

===============
2010-04-18追記
まとめました。
ゼロから構築する rails環境
===============

いつもの通りにCentOSにrailsを入れようかな~、フンフン♪~、と思って構築しだしたのですが、何か大変だったのでメモ。(全部rootで作業してます。)

ほとんどまっさらのCentOSにyumでrubyインストールしてgems入れようとしたら面倒でした。

# yum install ruby ruby-devel rdoc
# wget http://production.cf.rubygems.org/rubygems/rubygems-1.3.6.tgz
# tar xzf rubygems-1.3.6.tgz
# cd rubygems-1.3.6
# ruby ./setup.rb
ERROR:  Expected Ruby version >= 1.8.6, is 1.8.5

rubyが1.8.6以上でないとダメらしい。
gems1.3.4までは通ってたと思うので、1.3.5か1.3.6からruby1.8.6以上が必要になったっぽい。
困ったことにyumでは1.8.6取れないので、ソースから1.8.7インストール。

# ruby -v
ruby 1.8.5
# yum remove ruby
# wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p249.tar.gz
# tar xzf ruby-1.8.7-p249.tar.gz
# cd ruby-1.8.7-p249
# ./configure --prefix=/usr
...
configure: error: no acceptable C compiler found in $PATH
...

コンパイラも無いって言うから、gccいれてインストール続行。

# yum install gcc
# ./configure --prefix=/usr
# make
# checkinstall --fstrans=no
-bash: checkinstall: command not found

今度はcheckinstallが無いのか。

# yum install checkinstall
No package checkinstall available.

コンパイルしろってか。

# wget http://www.asic-linux.com.mx/~izto/checkinstall/files/source/checkinstall-1.6.2.tar.gz
# tar xzf checkinstall-1.6.2.tar.gz
# cd checkinstall-1.6.2
# make
# make install

OK。
ちゃんとRPMパッケージ作っとかないとね。
後々大変だから!

# cd /tmp/ruby-1.8.7-p249
# ./configure --prefix=/usr
# make
# checkinstall --fstrans=no

あれ?RPMパッケージできてこない・・・。
よく見たら、rpmbuildがないとエラーが。。

*** The "rpmbuild" program is not in your PATH!
*** RPM package creation aborted

で、よく考えたらcheckinstall自体のパッケージ作るのも忘れてた・・・。
make installは通ってるみたいだし、もういいや。
(yum install rpm-buildすればOKみたい。)

# ruby -v
ruby 1.8.7
# cd /tmp/rubygems-1.3.6
# ruby ./setup.rb
RubyGems 1.3.6 installed
# gem install rails
`gem_original_require': no such file to load -- zlib (LoadError)

またエラーが。今度はzlibがロードできないと。。
zlibへのパス通してまたmakeやり直し・・・。

# cd /tmp/ruby-1.8.7-p249/ext/zlib
# ruby extconf.rb --with-zlib-include=/usr/include -with-zlib-lib=/usr/lib
checking for deflateReset() in -lz... no
checking for deflateReset() in -llibz... no
checking for deflateReset() in -lzlib1... no
checking for deflateReset() in -lzlib... no
checking for deflateReset() in -lzdll... no

何これ?zlibないの?

# yum install zlib
Package zlib-1.2.3-3.i386 already installed and latest version
Nothing to do

あるって言ってるよ?

# yum list | grep zlib
...

何か色々出てきた。それっぽいzlib-develをインストールしたらOKだった。

# yum install zlib-devel
# ruby extconf.rb --with-zlib-include=/usr/include -with-zlib-lib=/usr/lib
# make
# make install
# gem install rails

ここまでの流れ、ここにも書いてた。
http://d.hatena.ne.jp/akm/20090410/1239353348

次回の時のためのメモ

・rpm-buildをインストールして、checkinstall用意。
・yumのrubyはもう使えない。
・rubyコンパイル時にzlibロードすること。

=== 追記 ===

rails環境構築の道は険しい。

・サーバ起動でエラー

no such file to load -- openssl.so

OpenSSLライブラリがロードできてない。

# yum install openssl-devel
# cd /tmp/ruby-1.8.7-p249/ext/openssl
# ruby extconf.rb --with-openssl-dir=/usr/local/ssl
# make
# make install

・mysql接続でエラー

no such file to load -- mysql

MySQLライブラリがロードできてない。
mysqlドライバも入れてなかった。

# yum install mysql-devel
# gem install mysql

あれ?素直に一発OK。
以前は通らなくて大変だった記憶が・・・。
gemもmysqlドライバもバージョンアップされてるみたいですし、通るようになったのかな?

一応その時の手順。

gemのMySQLドライバのインストール先調べて再コンパイル
# gem env
- GEM PATHS:
 - /usr/lib/ruby/gems/1.8
# cd /usr/lib/ruby/gems/1.8/gems/mysql-2.8.1/
# which mysql_config
/usr/bin/mysql_config
# ruby extconf.rb --with-mysql-config=/usr/bin/mysql_config
# make
# make install

mysql-2.8.1パッケージにはextconf.rbが見つからない。
よく分からないけど素直に通ったので良しとすることに。

2010年3月10日水曜日

Concrete5のメニューを画像に

Concrete5
http://concrete5-japan.org/
http://www.concrete5.org/

Concrete5すばらしいプロダクトだと思うのですが、プラグインが少なかったり日本語情報が少なかったりしてまだまだ使い辛い。

標準でautonavというメニュー生成のblock(機能)が提供されているのですが、メニューを画像に変更することがどうやらできない(のか?)。

メニューを固定で編集してしまうことは容易なのですが、せっかくのCMSなんで、もう少しスマートに解決したい。

ということで、autonavのカスタムテンプレートとして対応する方法をご紹介。

Image Navigation Items

詳しくはリンク先を参照して頂いて、以下、要点のみ。

1. ページ追加属性追加

「ページとテーマ」>「ページタイプ」>「ページ属性情報追加」
から「ファイル/画像」形式で通常状態のメニュー画像と当該画面表示中のメニュー画像を設定する属性を追加。

2. 画像設定

ページ編集画面の「ページ設定」>「カスタム項目」から1.で設定した画像設定項目を追加して、ファイルを設定。

3. カスタムテンプレート追加

autonavのカスタムテンプレートを追加する。

/concrete/blocks/autonav/view.php

/blocks/autonav/templates/image_navigation.php
などとして、編集

$linkTextActive = $ni->getName();
$linkTextInactive = $ni->getName();
 
$picOn = $_c->getAttribute('pic_on');
$picOff = $_c->getAttribute('pic_off');
 
if ($picOn) {
  $linkTextActive = '&#60;img src="' . $picOn->getURL() . '" alt="' . $linkTextActive . '"/>';
}
if ($picOff) {
  $linkTextInactive = '&#60;img src="' . $picOff->getURL() . '" alt="' . $linkTextInactive . '"/>';
}
 
if ($c->getCollectionID() == $_c->getCollectionID()) { 
 echo('
<li class="nav-selected nav-path-selected"><a class="nav-selected nav-path-selected" href="' . $pageLink . '">' . $linkTextActive . '</a>');
} elseif ( in_array($_c->getCollectionID(),$selectedPathCIDs) ) { 
echo('
<li class="nav-path-selected"><a class="nav-path-selected" href="' . $pageLink . '">' . $linkTextActive . '</a>');
} else {
echo('
<li><a href="' . $pageLink . '">' . $linkTextInactive . '</a>');
}

4. カスタムテンプレート適用

画像メニューを表示させたいautonavブロックをクリックして「カスタムテンプレート」適用

2010年3月9日火曜日

ソースコード表示テスト

Bloggerでも、コードハイライトしてみる。by google-code-prettifyより

Javaのソース
/**
  * SELECTクエリ実行
  * @return ResultSet 結果セット
  */
 private ResultSet executeSelectForBigTable() {
  Query query = new Query(kind);
  List preparedConditions = preparedOption.getPreparedConditions();
  for (PreparedCondition preparedCondition : preparedConditions) {
   query.addFilter(preparedCondition.getName(), preparedCondition.getOperator(), preparedCondition.getValue().toString());
  }
  return new ResultSet(getDatastoreService().prepare(query).asIterable(), preparedOption);
 }

ブログはじめます

最後にブログ書いたのは数年前ですが、ASPも増えてますし、管理画面も使いやすくなってますし、進化してますね。
いつまで続くか分かりませんが、技術系の備忘録を中心に個人的なメモとして。