開発メモや日ごろ思ったことなどを書きためるサイトです。サイト管理者は、SE、Web開発者。最近はiPhone、Android開発に興味が出てきてすこしずつ勉強中。
複雑なフォームのパラメータを持ちまわりたかったので、cakephperの日記(CakePHP, MongoDB, Lithium)さんのよくある確認画面でのhiddenデータの持ち回り ver2を改造させていただき無制限でネストしてパラメータに押し込む用にしてみた。
class FormhiddenHelper extends AppHelper {
var $helpers = array('Form');
function hiddenVars() {
$ret = "";
$keyStack = array();
$this->_hiddenVarsNestParse($this->data, $keyStack, $ret);
return $ret;
}
function _hiddenVarsNestParse($data, &$keyStack, &$ret) {
if (is_array($data)) {
foreach ($data as $key => $val) {
array_push($keyStack, $key);
$this->_hiddenVarsNestParse($val, $keyStack, $ret);
array_pop($keyStack);
}
} else {
$ret .= $this->Form->hidden(implode('.' ,$keyStack)) . "\n";
}
}
}
CakePHPの動作が難解なのでめも。
1.同一モデルの複数レコードの保存
※insertしたいのでidは指定しない
$data = array(
'Article' => array(
'0' => array(
'name' => 'a',
'price' => 1000),
'1' => array(
'name' => 'b',
'price' => 5000)));
$this->Article->saveAll($data);
2.単一レコードに結びつく複数モデルの保存
Categoryに結びつくArticleを作成
$data = array(
'Category' => array(
'id' => 1),
'Article' => array(
'0' => array(
'name' => 'a',
'price' => 1000),
'1' => array(
'name' => 'b',
'price' => 5000)));
$this->Article->saveAll($data);
ここで一つ問題が発生
Articleモデルのarticle.phpのvalidateに下記のような記述をした。
$validate = array(
'category_id' => array(
コントローラーの応答にリダイレクトが含まれるとtestActionを実行したときにテストケースが終了してしまうという落とし穴がある。対処方法がリンク先にいろいろ書いてあるが、とりあえず簡単な対処方法が見つかった。
testActionで呼ぶURL部分でcake bakeで作成されたテストケースのTest◯◯Controllerクラスを呼ぶように書き換える。
変更前: $result = $this->testAction('/admin/categories/xxxx');
変更後: $result = $this->testAction('/admin/test_categories/xxxx');
#categories_controller.test.php
class TestCategoriesController extends CategoriesController {
var $autoRender = false;
function redirect($url, $status = null, $exit = true) {
$this->redirectUrl = $url;
}
}
class CategoriesControllerTestCase extends CakeTestCase {
<省略>
function testSomeCase() {
$result = $this->testAction('/admin/test_categories/xxxx');
}
リダイレクトを前提とするテストケースでtestActionに'return' => 'var'を設定するとエラーになる。ビューが呼ばれないのにビューの変数を読み込もうとしてしまったのが原因みたい。
Androidのプログラミング格闘中、Twitterのリーダーを作ってみているがいきなりハードルを上げすぎたせいで悪戦苦闘。
どうしてもインターネットにアクセスできずブレークはってExceptionの中身をダンプしてやっと手がかりつかんだ。
java.lang.Throwable.detailMessage: "Permission denied (maybe missing INTERNET permission)"
作成したプログラムからインターネットアクセスするにはパーミッションを与える必要があるようだ。
AndroidManifest.xmlに下記の記述ををmanifestエレメント直下に追記するとインターネットアクセス成功
<manifest>
〜省略〜
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
</manifest>
参考: WillFeedさん
Android開発環境メモ
UTF8が入ったクラスファイルをうまくコンパイル出来なかったので。
default.propertiesでAndroidのバージョンを設定する。
このコマンドでバージョンのリストを確認することができる。
$ android list
出力結果はこんな感じ
id: 5 or "android-6"
Name: Android 2.0.1
Type: Platform
API level: 6
Revision: 1
Skins: HVGA (default), QVGA, WQVGA400, WQVGA432, WVGA800, WVGA854
プロジェクトのdefault.propertiesに
target=android-6
と記述
/Applications/android-sdk-mac_86/platforms/android-2.0.1/templates/android_rules.xmlを編集する。
<javac encoding="ascii" target="1.5" debug="true" extdirs=""
↓
<javac encoding="utf-8" target="1.5" debug="true" extdirs=""
パスの中の"android-2.0.1"の部分は"android list"コマンドの出力の中の該当のバージョンのNameの部分を参照
参考 少年よ大志を抱けさん
viewDidAppearについて数時間はまったのでメモ。
二つのUIViewControllerのサブクラス、FirstViewControllerとSecondViewControllerを準備してFirstViewControllerからSecondViewControllerに遷移させた直後にpresentModalViewControllerを呼び出して別画面を開くことを考えた。
最初、SecondViewControllerに下記のようにした。
- (void)viewDidLoad {
ThirdViewController *thirdViewController = [[TherdViewController alloc]
initWithNibName:@"ThirdView" bundle:nil];
[self presentModalViewController:thirdViewController animated:YES];
[thirdViewController release];
[super viewDidLoad];
}
これだとうまく行かない。viewDidLoadが呼ばれた時点ではSecondViewControllerのビューが呼び出されていないのでmodalがうまく行かない。
なので、こういうふうに書いてみた。
- (void)viewDidAppear:(id) animated {
ThirdViewController *thirdViewController = [[TherdViewController alloc]
initWithNibName:@"ThirdView" bundle:nil];
[self presentModalViewController:thirdViewController animated:YES];
[thirdViewController release];
[super viewDidAppear: animated];
}
が、うまく行かなかった。viewDidAppearメソッドにブレークポイントを入れてみたがどうやらこれ自体が呼ばれていないらしい。ここから数時間ネットの海を徘徊することになるのだが、全く情報をつかめなかったので色々なところをいじくりまわしたら解決策を発見した。
SecondViewControllerを呼び出しをこのように書き換えた。
[self.viwe addSubview:secondViewController.view];
↓
[self presentModalViewController:secondViewController animated:NO];
どうやらaddSubviewでは少なくとも呼び出し直後はviewDidAppearが呼び出されないらしく、
presentModalViewControllerをつかえばすぐに呼び出されるらしい。
学んだこと
1.viewDidLoadにmodal処理を書いてもうまく動作しないのでviewDidAppearに記述する。
2. addSubViewで呼び出されたコントローラーのviewDidAppearは少なくともすぐには呼び出される
ことはないない。presentModalViewControllerはすぐに呼び出される。
iPhoneプログラミング初めて1,2週間の人間がこんな事書いて良いのかしら。ちゃんとした対処法があれば教えていただきたいです。
Entityが複数のEntity Groupに所属できないGAEでの分散トランザクションの実装パターン、とても参考になる。
・Nick's Blogさん
Distributed Transactions on App Engine
・Song of Cloudさん
送金のトランザクション処理パターン
以前のエントリの問題も購入履歴に利用ポイント、会員マスタにポイント利用履歴を持たせて、購入履歴から会員マスターに利用ポイントを転送するという実装にすれば同じパターンが適用できるはず。
と、思ったけどよく考えたら解決していない。
会員が持っている残りのポイントが参照したときに最新の状態であることを保障する手立てを考える必要がある。
Google App Engine + Struts2の 2.1.8でConvention pluginをつかってstruts.xmlにアクションの定義をしないとこんなメッセージが出力される。
2009/12/21 17:16:08 com.opensymphony.xwork2.util.logging.commons.CommonsLogger warn
WARNING: No configuration found for the specified action: 'Index' in namespace: '/'. Form action defaulting to 'action' attribute's literal value.
struts.xmlにこんな感じで定義してあげると、エラーが表示されない。
<package name="Index" extends="struts-default" namespace="/">
<action name="Index" class="jp.mrk.gae.test.action.IndexAction">
<result>/WEB-INF/content/index.jsp</result>
</action>
</package>
定義してしまうとConvention pluginの意味がないのでどうしたものか。
Info程度で良いメッセージをWARNINGで表示しているのか、それとも根本的に何かがやってはいけないコトをしているのだろうか。WEB上に悩んでいる人はいるけれどは解決策が見つからなかった。
CakePHPのコアをいじらないでブラウザ終了時にセッション終了する方法がわかったので記載する。
CakePHPはSecurity.level lowまたはmediumだとブラウザを閉じても、セッションが終了しない。これは、PHPの"session.cookie_lifetimeがセキュリティレベルがmediumの時は7日間、lowの時は25年間ぐらいに設定されるためだ。
散々悩んだ挙句にCakePHPのコアのソースを読んでみて、cake/libs/session.phpでいろいろやっていることが分かったので解決方法をここで書きたいと思う。
CakePHPのconfig/core.phpでSession.saveをcakeに設定すると下記の通りセッションの設定がされる。
ini_set('session.use_trans_sid', 0);
ini_set('url_rewriter.tags', '');
ini_set('session.serialize_handler', 'php');
ini_set('session.use_cookies', 1);
ini_set('session.name', Configure::read('Session.cookie'));
ini_set('session.cookie_lifetime', $this->cookieLifeTime);
ini_set('session.cookie_path', $this->path);
ini_set('session.auto_start', 0);
ini_set('session.save_path', TMP . 'sessions');
この設定のsession.cookie_lifetimeの部分だけ入れ替えたいので"config/myapp_session.php"というファイルを準備し上の記述を丸々コピーしてsession.cookie_lifetimeの部分のみ書き換える。
config/myapp_session.phpの内容
------------------------------------------------------
ini_set('session.use_trans_sid', 0);
ini_set('url_rewriter.tags', '');
ini_set('session.serialize_handler', 'php');
ini_set('session.use_cookies', 1);
ini_set('session.name', Configure::read('Session.cookie'));
ini_set('session.cookie_lifetime', 0); // ← この部分を0にした
ini_set('session.cookie_path', $this->path);
ini_set('session.auto_start', 0);
ini_set('session.save_path', TMP . 'sessions');
------------------------------------------------------------
次に/config/core.phpを開き下記の通り変更する
変更前: Configure::write('Session.save', 'cake');
変更後: Configure::write('Session.save', 'myapp_session');
これでmyapp_session.phpの内容を読み込んでくれて、ブラウザを閉じるとセッションも終了するようになる。
以下蛇足
myapp_session.phpが読み込まれる仕組み。
CakePHPコアのsession.phpではSession.saveで不明なタイプが設定されている場合に
下記のプログラムが実行され、"Session.saveで設定された名称" + .phpが読み込まれる
cake/libs/session.php __initSession()の最後のほう
if (empty($_SESSION)) {
$config = CONFIGS . Configure::read('Session.save') . '.php';
if (is_file($config)) {
require_once ($config);
}
}
# 2009/10/3 わかりにくかったので、多少文章変更
Drupalでトラックバックspam対策で承認制にしているのだが、それでも一日数十件のトラックバックが登録されてしまい、確認するだけでもかなり面倒だ。なので承認されていないトラックバックが過去に5回以上蓄積されているホストからのアクセスは自動的にアクセス拒否するようにバッチを作成した。
バッチの中身はこちらトラックバック spam対策
# 2009-01-27 追記
ここ二日間は登録されてしまうのトラックバックの数が一日に1件程度にまで減ったので、とりあえず効果があった。