いろんな会社のいろんな人の話を聞けるのはやっぱり楽しい!YAPC::Asia Tokyo 2012に参加して来た!

こんにちは!こんにちは!monmonといいます!



2009年に初めて参加して、一昨年去年に引き続き今年もスタッフとして参加させてもらいました。



3年続けてスタッフ参加だったためメインホールのリーダー担当を任されたんですが、今回は初めての場所ということもあって失敗しないか心配で仕方なかったです。



と、失敗してない風なこと言いましたが、2日目におもいきりやらかしました。
「一番長いのが40分と思い込んでた」+「東大の学食に行ったら長蛇の列で帰って来たのが始まる5分前だった」の結果、
Perl 今昔物語のときに勘違いして10分前カンペ出してしまい、馮さんが941さんに「あれ?60分でしたよね?」と聞かなくてはいけないはめに。

すみません、僕のカンペのせいですみません。
慣れない学食なんて行くもんじゃないなってのと、ちゃんと時間は確認しないとダメだと心に刻みました。



ただ、こういう失敗はあったものの、スタッフ全体の動きはスムーズでした。
何かあればLINEでやり取りをして解決していたため、以前のようにバタバタすることがなかったなぁと思います。
# 去年はfacebookメッセージでやり取りをしたんですが、LINEの方がみんなに合っていたからなのか、去年よりもやり取りが活発でした。



会場の準備や片付けについても、牧さん941さんをはじめとする実行委員の人たちのノウハウとスタッフの数の多さのおかげでホントにビックリするくらいな順調具合。最後も「本当にもう終わっちゃうの?!」という感じであっという間に終わってしまい「でかいイベントなのに手際良くてすごいなぁすごいなぁ」という第三者目線で感動してました。

準備のワンシーンとして恒例となったZIGOROuメソッド(いわゆるバケツリレー)でのノベルティ詰めも貼っておきます。

この流れ作業、近くの人とワイワイ話すきっかけにもなってスタッフ間の交流に一役かっているんじゃないだろうかと感じてます。
スタッフ間でのぼっち対策ですね。



そうそう、話が前後しますが、今回僕は少し早めに着いたので他の人が来る間あんちぽさんのSQALEプロマイドを一人で堪能してました。

これはその夜の風景。

あんちぽさんに「どれが当たりですか?」とみんなで聞いているところです。
# ちなみに当たりは草原でかわいいポーズ取っているやつ。
# Ricoさんがコンプさせてたので参考:https://twitter.com/RicoImazu/status/251875701408550912



話がそれた。



去年も一昨年も思ったんですが、941さんのイベントを回す動きはすごいですね。準備の仕方、指示の出し方、問題回避ととっさの対応、業者さんへの直接ダメ出し、と、イベントをやるような人にとっては勉強になることたくさんあるだろうなぁと改めて思いました。



さて、スタッフの話はこの辺にして。
今回はメインホール担当だったのでトークもいくつか聞くことができました。



僕の印象に残っているトークはtypesterさんの「Perlハッカーは息をするようにCPANモジュールを書く。」です。
既にスライドは上がっていますが、
「そのモジュールを作った熱い想いをドキュメントに書こう!」
の流れはスライドではなく動画で観た方がいいので動画が上がったら観てみてください。



このトークの中に出てくる「ドキュメントは熱い想いをぶつけるところ」って表現がとても好きです。
僕は仕事をしているときに「こういうやり方もできたんだけど、〜って理由があったからこのやり方でやったんだ」というのを知るとだいぶ仕事がやり易くなります。
作った人のポリシーみたいなものが見えていると、それだけで「なんでここはこうなの?」的な面倒なやり取りがだいぶ減ってコストが下がるんです。
そういう相手の場合、その人が作る物がどういうものなのかがだんだんわかってきて、ますます仕事がやりやすくなり、そしてもう何て言うかとても楽なんです。なんでですかね、癖みたいな物がわかるってことなんですかね。
なので「ドキュメントに熱い想い」ってのはいい表現だなぁと。
とてもいい表現なので会社でも使いたいなと思っています。



もう一つ印象に残っているトークは最後のmizzyさんの話。

話を聞きながら

ってtweetもしたし、社内の日報にも「新人の子たちは動画観るといいよ」って感想を書きました。



去年のhidekさんの話もそうだけど、YAPCに来るといろんな会社のいろんな人の思いが聞けて楽しいです。
飲みに行ったりすれば聞けるんだろうけど、こんなにいっぺんにいろんな人の話が聞ける機会ってやっぱりそうそうないから、「どういう考え方でコードを書いている」「どういう考え方でサービスを作っている」「どういう考え方で行動している」みたいな話が聞けるのは貴重だなと感じています。
しかも一方通行で聞くだけではなく、質問すれば聞きたいことがもっと聞ける。とても楽しいです。



スタッフも楽しいけど話す側も楽しそうだから次回はそっちもやろうと思います。
次回のYAPC::Asiaも楽しみです!



最後に、今回も牧さん941さん長い期間かけての準備おつかれさまでした!ありがとうございました!

他のbranchの内容を調べる時はgit grepしてgit showする

「前に書いたmethodがなくなってるなぁ」
「あぁ、あれ違うbranchで作ったmethodか」
みたいなことを調べる時にタイトルのようにやればいいみたい。

まずはgit grepで違うbranchから探したいものを検索

 (git)-[hotfix/2.4.20]-[12:30]% git grep isUniqueWith feature/m48                                                                     [/Applications/MAMP/htdocs/lib]
feature/m48:app/Model/Enticement.php:            'isUniqueWith' => array(
feature/m48:app/Model/Enticement.php:                           'rule' => array('isUniqueWith', 'company'),
feature/m48:app/Model/Enticement.php:    public function isUniqueWith($data, $fields)

CakePHPでDBのExceptionが起こった時にどういうSQLを投げたためかをログに吐く

何か変なqueryをMySQLに投げてしまって

2012-05-26 18:23:21 Error: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`test`.`user_logs`, CONSTRAINT `user_logs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))

みたいな感じでPDOExceptionが発生した場合に、どんなSQLを実行したのかがわからなくて困ったので調べた。



debugモードであればqueriesLogに保存されてそれを覗けばわかるのだけど、本番だとqueriesLogには保存されないのでそれだとダメ。
ググっても「queriesLog覗けばいいよ」とか「getLog()実行すればいい」的なのばかり出て来てなかなか欲しい情報にたどり着けず。
# というか、結局エラーが出た時にSQLが調べられればいいのでdebugモードのときみたいに常にある必要はない



で、ソース見たらあった。

<?php
// https://github.com/cakephp/cakephp/blob/master/lib/Cake/Model/Datasource/DboSource.php#L449
		} catch (PDOException $e) {
			if (isset($query->queryString)) {
				$e->queryString = $query->queryString;
			} else {
				$e->queryString = $sql;
			}
			throw $e;
		}

PDEOExceptionが起こった時に$eのqueryStringに突っ込んでる。
なのでexceptionで捕まえた時にコレをそのままログに吐けばいい。



ってことで、AppModelの中でException捕まえて、その中で

<?php
CakeLog::write('error', $e->getMessage());
CakeLog::write('error', $e->queryString);

みたいな感じでログに吐くことにした。
結果、

Error: SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (`test`.`user_logs`, CONSTRAINT `user_logs_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))
Error: INSERT INTO `test`.`user_logs` (`is_error`, `modified`, `created`) VALUES ('1', '2012-05-26 18:23:20', '2012-05-26 18:23:20')

こんな感じでログが吐かれる

CakePHPでwhere句のINの並び順で結果をsortしたいときはORDER BY FIELDを使う

where句のINの並び順で結果をsortしたいときはORDER BY FIELDを使うの続き

findのorderの所で

<?php
array('order' => array('FIELD(Country.id, 4, 1, 5, 3, 6, 2)'))

と書けばいい。

参照

http://cakebaker.42dh.com/2008/06/10/order-by-field/

Ok, let’s do some examples to learn more about it. We will use the following countries table:
[1] => USA
[2] => Germany
[3] => Russia
[4] => Austria
[5] => China
[6] => Switzerland
Now, we don’t want to sort them by the id, but by some “strange” order: Austria, USA, China, Russia, Switzerland, Germany. For this purpose we can use “order by field”: the first parameter is the column name and all following parameters are values of the respective column. In CakePHP it is done in the following way:

<?php
$this->Country->find('list', array('order' => array('FIELD(Country.id, 4, 1, 5, 3, 6, 2)')));

And we get the expected result:
[4] => Austria
[1] => USA
[5] => China
[3] => Russia
[6] => Switzerland
[2] => Germany

where句のINの並び順で結果をsortしたいときはORDER BY FIELDを使う

これ、できないもんだと思って調べてもいなかった。

ORDER BY FIELD(カラム名, INのリスト...)

でいけるのね。便利だー。

http://lists.mysql.com/mysql/209784

SELECT id, start_date FROM iddt WHERE id IN
('109k7','s3x6','sxmns','wt57')
ORDER BY FIELD(id,'109k7','s3x6','sxmns','wt57')

dotfilesをgithubで管理する

やろうやろうと思いつつ、1年くらいずっとやってなかったので会社のサーバの設定をするタイミングで試しに手を付けた。
# 設定ファイルとかはコピペが多いので管理するほどでもなかったのだけども

流れ

1. まずはdotfilesディレクトリを作ってそこにdotfilesなファイルたちを放り込む

[9:16]% cd ~                                                                                                                [~]
[9:16]% mkdir dotfiles                                                                                                      [~]
[9:16]% mv .zshrc dotfiles/.                                                                                                [~]
[9:16]% mv .vimrc dotfiles/.                                                                                                [~]

2. そのあとにそれらにシンボリックリンクを張るようなシェルスクリプトを書く
# .gitが入らないようしたいのでgrep -v 'dotfiles/.git$'を忘れない

[9:39]% cat ./dotfiles/setup.sh                                                                                             [~]
#!/bin/bash

for file in `find $HOME/dotfiles -name '.*' | grep -v 'dotfiles/.git$' | perl -nle 'm!dotfiles/(.+)$! and print $1'`; do
    ln -s $HOME/dotfiles/$file $HOME/$file
done
[9:39]%    

3. 一旦シェルスクリプトを実行してリンクを張る

sh -x ./dotfiles/setup.sh

4. githubのページに行き、dotfilesというリポジトリを作成

5. githubのページに書いてある手順通り、dotfilesディレクトリに移動し、git init

[9:55]% cd dotfiles                                                                                                         [~]
[9:58]% git init                                                                                                   [~/dotfiles]
Initialized empty Git repository in /Users/no-kumagai/dotfiles/.git/
 (git)-[master]-[10:03]%                                                                                           [~/dotfiles]

6. README.markdownを作成

[9:54]% cat README.markdown                                                                                      [~/dotfiles]
1. cd $HOME
1. # git clone
1. sh -x ./dotfiles/setup.sh
[9:54]%   

7. addしてcommit

 (git)-[master]-[10:04]% git add .                                                                                 [~/dotfiles]
 (git)-[master]-[10:04]% git commit -m 'first commit'                                                              [~/dotfiles]
[master (root-commit) b41fc1a] first commit
 4 files changed, 77 insertions(+), 0 deletions(-)
 create mode 100644 .vimrc
 create mode 100644 .zshrc
 create mode 100644 README.markdown
 create mode 100755 setup.sh
 (git)-[master]-[10:09]% 

8. そしてpush

 (git)-[master]-[10:09]% git remote add origin git@github.com:monmon/dotfiles.git                                  [~/dotfiles]
  git push -u origin master                                                                                                    
 (git)-[master]-[10:10]%   git push -u origin master                                                               [~/dotfiles]

Counting objects: 6, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 1.22 KiB, done.
Total 6 (delta 0), reused 0 (delta 0)
To git@github.com:monmon/dotfiles.git
 * [new branch]      master -> master
Branch master set up to track remote branch master from origin.
 (git)-[master]-[10:10]%                                                                                           [~/dotfiles]
 (git)-[master]-[10:10]%      

9. おつかれさまでした。

CakePHPで動的にBehaviorを切り替えてModelの振舞いを変えるときはunload、loadを使う

CakePHP DocumentのBehaviorsの項にも書いてありますが、「ある時はこういう処理をして欲しいけど、違う時は別の処理をして欲しい」みたいな話。

やりたいこと

  • あるModelがあって、今はAという処理だけあれば良いのだけど、今後BやCという処理も増やしたい
    • 例えば「自動販売機」というModelがあって、今は「缶を売る」という処理だけだけど、今後「ビンを売る」「ペットボトルを売る」という処理も増やしたい

こんな時はデザインパターンStrategy パターンが使えそうなのだけど、CakePHPでやる時にはBehaviorを使えば似たようなことできるのかな?と思って調べて試してみた。

CakePHPのドキュメントを確認

Behaviorsのドキュメントを読むと、

However, we may need to “detach” behaviors from our models at runtime. Let’s say that on our previous Category model, which is acting as a Tree and a Translate model, we need for some reason to force it to stop acting as a Translate model:

<?php
// Detach a behavior from our model:
$this->Category->Behaviors->unload('Translate');

That will make our Category model stop behaving as a Translate model from thereon.

「実行時にmodelからbehaviorをdetachする必要があったらunloadすればその振舞いは止まるよ」と書いてある。
そして、

Just as we could completely detach a behavior from a model at runtime, we can also attach new behaviors. Say that our familiar Category model needs to start behaving as a Christmas model, but only on Christmas day:

<?php
// If today is Dec 25
if (date('m/d') == '12/25') {
    // Our model needs to behave as a Christmas model
    $this->Category->Behaviors->load('Christmas');
}

We can also use the load method to override behavior settings:

<?php
// We will change one setting from our already attached behavior
$this->Category->Behaviors->load('Tree', array('left' => 'new_left_node'));

「loadを使えば新しいbehaviorをattachすることもできて、設定も渡せるよ。」と書いてある。


なるほどなるほど。

ということで、具体的に試してみる

単純な例で試したかったので、Head Firstデザインパターンの1章にあるDuckの例(ModelDuck)を参考にした。

登場人物
  • Duckクラス
    • 色んな鴨の親クラスになる抽象クラス。
  • ModelDuckクラス
    • 鴨の実装。模型の鴨。飛ぶ手段がないためdefaultでは飛べず、performFly()すると「飛べません」と返す。
  • FlyNoWayBehaviorクラス
    • performFlyの振舞いの実装。fly()すると「飛べません」と返す。
  • FlyRocketPoweredBehaviorクラス
    • performFlyの振舞いの実装。fly()すると「ロケットで飛んでいます!」と返す。
  • ModelDuckTestクラス
    • 確認するためのテストクラス。
クラス図

ざっくりこんな感じ。ModelBehaviorは出て来ないけど、actsAsプロパティの配列の中身はModelBehaviorの実装なのでクラス図には入れた。

ソースコード

まずはテストコード。
基本はbakeで作られたテンプレートなので、「testModelDuckは飛ぶ振舞いが変わる」というmethodの所だけ実装。
やってることは、

  1. 初期は「飛べない」
  2. 「飛べない」振舞いをunloadし、「ロケットで飛べる」振舞いをloadし、振舞いを変える
  3. そうすると「ロケットで飛べる」

という確認だけ。このテストが通れば今回やりたいことはOK。

<?php
// app/Test/Case/Model/ModelDuckTest.php
App::uses('ModelDuck', 'Model');

class ModelDuckTestCase extends CakeTestCase {
	public $fixtures = array(
    );

	public function setUp() {
		parent::setUp();

		$this->ModelDuck = ClassRegistry::init('ModelDuck');
	}

	public function tearDown() {
		unset($this->ModelDuck);

		parent::tearDown();
	}

    public function testModelDuckは飛ぶ振舞いが変わる()
    {
        debug(__FUNCTION__);

        $this->_modelShouldNotFly();
        $this->_changeBehavior();
        $this->_modelShouldFly();
    }

    private function _changeBehavior()
    {
        $this->ModelDuck->Behaviors->unload('FlyNoWay');
        $this->ModelDuck->Behaviors->load('FlyRocketPowered');
    }

    private function _modelShouldNotFly()
    {
        $this->assertEquals('飛べません', $this->ModelDuck->performFly(), '飛べない状態になっているか');
    }

    private function _modelShouldFly()
    {
        $this->assertEquals('ロケットで飛んでいます!', $this->ModelDuck->performFly(), 'ロケットで飛べているか');
    }
}

次に具体的な鴨の親にあたるDuckクラス。abstrat。
fly()を呼ぶためのperformFly()が定義されている。具象の鴨を実装する時にactsAsまたはloadでBehaviorを読み込み、fly()の振舞いが決まる。

<?php
// app/Model/Duck.php
abstract class Duck extends AppModel {
    public function performFly()
    {
        return $this->fly();
    }
}

今回の具象クラスであるModelDuckクラス。
CakePHPの都合だけども、今回はDBを使わないのでuseTableはfalse。
あとは自分のfly()のdefault振舞いをFlyNoWay(飛べません)にする。

<?php
// app/Model/ModelDuck.php
App::uses('Duck', 'Model');

class ModelDuck extends Duck {
    public $useTable = false;
    public $actsAs = array('FlyNoWay');

    public function display()
    {
        return '模型の鴨です';
    }
}

「飛べない」振舞いを表すクラス。
setupはactsAsに指定した時やloadを実行した時に呼ばれる初期値。CakePHPのdefaultのままなので今回は特に何もしてない。

<?php
// app/Model/Behavior/FlyNoWayBehavior.php
class FlyNoWayBehavior extends ModelBehavior {
    public function setup(Model $Model, $settings) {
        if (!isset($this->settings[$Model->alias])) {
            $this->settings[$Model->alias] = array(
                'option1_key' => 'option1_default_value',
            );
        }
        $this->settings[$Model->alias] = array_merge(
            $this->settings[$Model->alias], 
            (array)$settings
        );
    }

    public function fly(Model $model)
    {
        return '飛べません';
    }
}

「飛べない」振舞いを表すクラス。
setUpはFlyNoWayBehavior同様。

<?php
// app/Model/Behavior/FlyRocketPoweredBehavior.php
class FlyRocketPoweredBehavior extends ModelBehavior {
    public function setup(Model $Model, $settings) {
        if (!isset($this->settings[$Model->alias])) {
            $this->settings[$Model->alias] = array(
                'option1_key' => 'option1_default_value',
            );
        }
        $this->settings[$Model->alias] = array_merge(
            $this->settings[$Model->alias], 
            (array)$settings
        );
    }

    public function fly(Model $model)
    {
        return 'ロケットで飛んでいます!';
    }
}

テストの実行

実行すると下のように上手くいきました。

% ./cakephp/lib/Cake/Console/cake -app app testsuite --tap app Model/ModelDuck

Welcome to CakePHP v2.0.4 Console
---------------------------------------------------------------
App : app
Path: /Applications/MAMP/htdocs/lib/app/
---------------------------------------------------------------
CakePHP Test Shell
---------------------------------------------------------------
TAP version 13
ok 1 - ModelDuckTestCase::testModelDuckは飛ぶ振舞いが変わる
1..1

まとめ

CakePHPでStrategyパターンみたいなことがやりたいときにはBehaviorのunloadとloadを使えば良さそう。
というか、それがBehaviorの元々の意図なんだろうけども。僕は今までそういう使い方してなかったので。