11 月 17

AIRアプリケーションで、現在のヴァージョンは、NativeApplicationインスタンスのプロパティapplicationDescriptorから調べることができます。
applicationDescriptorはXML形式のデータなので、E4Xを使って参照しましょう。
具体的には以下のような感じ。

var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = appXML.namespace();
lblAppVersion.text = appXML.ns::version;
lblAppName.text = appXML.ns::name;
11 月 12

YouTube 新機能のご紹介:ディープリンク

リンク先のYoutube動画の再生開始位置を指定して再生する機能が追加された。
やり方は簡単で、リンクアドレスに#t=0m53という形式で分・秒を指定する文字列を追加する。

さらにコメント欄にも、どの再生位置に対してのコメントか判るように時間指定する機能も合わせて追加された。

他の動画共有サービスでも取り入れられるかな?

10 月 10

ZendFrameworkが1.6からWildfireというものをサポートというものをするようになった。
ざっと読んだ感じでは、WirldfireというのはFirePHPをZendFrameworkに統合する過程で生まれたプロジェクトで、データ交換のためのフォーマットやプロトコルを定めたWildfireチャンネルを使ってコミュニケーションを実現させるようなものらしい。

まぁ、とりあえずは、FirePHPの仕組みを汎用的にして、データ交換する技術とでも理解しておく。(間違ってるかも・・・)
FirePHPはサーバ側でhttpヘッダーにログ情報を書き出し、
それをFirefoxのアドオンFirePHPで読み取り、FireBugのコンソールに表示してくれるというシロモノで、
サーバ側のPHPライブラリとFirefoxがわのFireBug+FirePHPのアドオンからなる技術
一応、PHPということになってはいるが、決められた形式のヘッダーさえ書き出せば、
他の言語でもブラウザでログを読むことができるので、PythonやJaxer向けのライブラリもあるようです。
ZendFrameworkだけでなく、cakePHPやCodeIgniter、PRADOなどなどFirePHPをサポートするフレームワークも
多いので、PHPで開発してる人は試してみる価値ありかもです。
今回はZendFrameworkと組み合わせて使ってみました。

準備 まずはダウンロード&インストール

  1. FireBugのインストール
  2. FirePHPのインストール

ここら辺は、とくに普通のアドオンをインストールするのと変わりません。
ただ使ってるFirebugとFirePHPのバージョンの違いのせいか、

僕の環境では最初コンソールにログが表示されませんでした。
最初はFireBug1.2.1+FirePHP0.1.2の組み合わせではログは表示されず、
FireBug1.2.1+FirePHP0.2.β.2に変えたら表示されるようになりました。

Zend Framework1.6では、FirePHPとコミュニケーションするためのZend_Log_FirebugとZend_Db_Profiler_Firebugの2つのコンポーネントを提供している。

Zend_Log_Writer_Firebugでログを書き出す。

Zend FrameworkではZend_LogコンポーネントがZend_Writerコンポーネントを経由してロギングを行います。Zend_Writreコンポーネントはログの出力処理を実装したもので、Zend_Log_Writer_Firebugコンポーネントもその1つです。ロギングを行うには以下のようなコードになります。

$logger = new Zend_Log();
$writer = new Zend_Log_Writer_Firebug();
$logger->addWriter($writer);

なんどもロガーを作るのが面倒なので、bootstrapの段階でロガーを作ってZend_Registryに登録しておいて、それを使いまわすのが楽

//bootstrap.php
Zend_Registry::set('logger',$logger); //ロガーを登録

とZend_Registryに登録しておいて、
使いたいときには以下のように

//ロガーを使うときは
$logger = Zend_Registry::get('logger');
$logger->log("Hello, world", Zend_Log::DEBUG);

Zend_Db_Profiler_Firebug

Zend_Db_Profiler_Firebugはデータベースのプロファイラとして使用することのできるコンポーネントで、データベースに対して発行したSQLクエリーを出力してくれます。以下のような感じでプロファイラをZend_DBオブジェクトに登録します。

$profiler = new Zend_Db_Profiler_Firebug('Laber for this profiler');
$profiler->setEnabled(true);
$db->setProfiler($profiler);

どんなヘッダー?

10 月 07

Zend Toolを使おうとしたら、php5ts.dllが原因でphp.exeが落ちるっ(汗

こういう風に、エラーが出ずにphpごと落ちる場合は、拡張モジュールが怪しいかったりする。
なので、拡張モジュールを1つづつ読み込むようにテストしてみると、どうやらphp_domxml.dllが原因らしい。
PHP5からはDOMが標準で読み込まれるようになっているのでDOMXML拡張モジュールは必要ないはずなのに、XAMPPに含まれるphp.iniには読み込むように設定されていた。
通常のphp5のパッケージだとDOMXMLモジュール自体含まれてないので、XAMPP固有の設定ミスなんだろうなぁ
XAMPPを使ってる方は、気をつけて。



10 月 06

いちいちサービスマネージャを起動してサービスを起動/停止させるのが面倒なんで、コマンドプロンプトから操作したいんだが、肝心のサービス名を忘れちゃってるなんてことありますよね?

Windowsにはsc(Service Managerの略)というコマンドでサービスをコマンドプロンプトから操作できるようなので、これを利用してサービス名を調べてやりましょう。たとえば、Apacheをどんなサービス名でインストールしたか忘れた場合。
sc query | grep Apache
とか打ってやれば、
C:>sc query | grep Apache
SERVICE_NAME: Apache2.2
DISPLAY_NAME: Apache2.2
てな感じでサービス名を検索できたりするわけです。
※sc query は現在アクティブなサービスのリストを表示する。
※grepはWindowsには標準では入っていないのでcygwinとかWindows Service for UNIXを入れるなりしてね。
9 月 25

デバッグモードを使う

Rubyにはグローバル変数$DEBUGが定義されている。これは、Rubyインタプリタに--debugオプションを渡すか、コード$DEBUGをtrueにすることで有効になる。

loggerを使う

Ruby標準ライブラリのLoggerを使って、ログデータをファイルまたは別のストリームに出力する。
ログレベルはFATAL,ERROR,WARN,INFO,DEBUGの5段階。
require 'logger'

log = Logger.new(STDOUT)
#log.level = Logger::ERROR
#log.level = Logger::FATAL
#log.level = Logger::WARN
#log.level = Logger::INFO
log.level = Logger::DEBUG

log.error("Error!!");
log.fatal("Fatal!!");
log.debug("Created logger")
log.info("Program started");
log.warn("Nothing to do!");

出力

E, [2008-09-25T21:37:18.168000 #5664] ERROR -- : Error!!
F, [2008-09-25T21:37:18.168000 #5664] FATAL -- : Fatal!!
D, [2008-09-25T21:37:18.168000 #5664] DEBUG -- : Created logger
I, [2008-09-25T21:37:18.168000 #5664]  INFO -- : Program started
W, [2008-09-25T21:37:18.178000 #5664]  WARN -- : Nothing to do!

Loggerは期間あるいはファイルサイズを元にrotateしてログファイルを交換することもできる。

Logger.new('logfile.log', 'monthly') #月ごとにログファイルを交換 他にdaily, weeklyが指定できる。
Logger.new('logfile.log', 5, 100 * 1024 * 1024) #100MBのログを5つまで保持する
Logger.new('logfile.log', 0, 100 * 1024 * 1024) #100MBでログファイルを交換
datetime_formatを変更することで日付フォーマットを変更できる。
log = Logger.new(STDOUT)
log.datetime_format = "%Y-%m-%d %H:%M:%S"

さらに、Logger::Formatterのcallをオーバーライドすれば、さらに出力を変更できる。

Kernel#callerでコールスタックを調べる。

Kernel#callerによって呼び出し元のメソッドを調べることができる。
class ClassA
	def ClassA.some_methodA
		ClassB.some_methodB
	end
end

class ClassB
	def ClassB.some_methodB
		ClassC.do_action
	end
end

class ClassC
	def ClassC.do_action
		puts 'show trace back'
		caller.each do |c|
			puts c
		end
	end
end

ClassA.some_methodA

出力
show trace back
trace_back.rb:9:in `some_methodB'
trace_back.rb:3:in `some_methodA'
trace_back.rb:22

Breakpointライブラリを使用する

メソッドbreakpointを実行すると、irbの対話型のRubyセッションに切り替わります。

require 'breakpoint'

$KCODE ='sjis'
class Foo
	def initialize(init_val)
		@instance_var = init_val
	end
	def bar
		test_var = @instance_var
		puts 'before break'
		breakpoint
		puts 'after break'
		puts "test_var: #{test_var}, @instance_var: #{@instance_var}"
	end
end

f = Foo.new('start')
f.bar

プロファイリング機能を使用する

アプリケーションにRubyプロファイラをインクルードすると、rubyはアプリケーション終了後に標準エラー出力にレポートを出力する。

require 'profile'

total=0
('a'..'zz').each do |seq|
	['a', 'b', 'c'].each do |i|
		if seq.index(i)
			total += 1
			break
		end
	end
end
puts "Total: #{total}"


出力

Total: 150
  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
 48.00     0.12      0.12      702     0.17     0.27  Array#each
 24.00     0.18      0.06     1952     0.03     0.03  String#index
 24.00     0.24      0.06        1    60.00   250.00  Range#each
  4.00     0.25      0.01      150     0.07     0.07  Fixnum#+
  0.00     0.25      0.00        1     0.00     0.00  String#<=>
  0.00     0.25      0.00      702     0.00     0.00  String#succ
  0.00     0.25      0.00        1     0.00     0.00  Kernel.puts
  0.00     0.25      0.00        2     0.00     0.00  IO#write
  0.00     0.25      0.00        1     0.00     0.00  Fixnum#to_s
  0.00     0.25      0.00        1     0.00   250.00  #toplevel

プロファイラは最も時間のかかるメソッドからリストアップする。

9 月 12

CodeIgniterのマイグレーションツール

CodeIgniterのWikiにマイグレーションの解説が載っていたので、おためし導入してみました。

http://codeigniter.com/wiki/Migrations/

インストール

ここ からzipアーカイブをダウンロードして解凍します。

ファイルの中身は以下の通り、

application/
	config/
		migrations.php - Migrations settings
	controllers/
		migrate.php - Main controller
	language/
		english/
			migrations_lang.php - English notices and messages
		espaol/
			migrations_lang.php - Spanish notices and messages
	example_migrations/
		001_create_articles_schema.php - Example migration on creating and dropping tables
		002_rename_articles.php - Example migration on renaming tables
		003_add_signature.php - Example migration on addign and removing table columns

applicationフォルダ以下をCodeIgniterのapplicationフォルダへコピーします。ただし、example_migrationsフォルダはサンプルなのでコピーする必要はありません。

つぎに、config/migrations.phpを開き、$config["migrations_enabled"]の値をTRUEにして、マイグレーション機能を有効にします。

$config["migrations_enabled"] = TRUE

つぎに、マイグレーションを記述したクラスを置いておくフォルダをつくり、$config["migrations_path"]にそのパスを指定します。デフォルトはapplication下のmigrationsフォルダが指定されています。

最後にマイグレーションを実行するコントローラcontrollers/migrate.php

を開いて、コンストラクタにdbutilヘルパーを読み込むための

$this->load->helper('dbutil');

を追加します。
これで、準備完了です。

使い方

まず、マイグレーションクラスを作成します。
マイグレーションクラスは、$config["migrations_path"]にしていしたフォルダに配置し、###_migration_unique_name.phpという形式のファイル名にします。
"###"の部分はマイグレーションのバージョンを表し、"miguration_unique_name"の部分はCodeIgniterの名づけルールに従います。

<?php
#fine name: 001_create_profile_schema.php
 
class Create_profile_schema{
	function up(){
		echo 'version up to 1';
		echo 'creating table profiles';
		create_table(
			'profiles',
			array(
				'id'=>array(INTEGER,NOT_NULL),
				'name'=>array(STRING, NOT_NULL),
				'birth'=>array(DATE, NOT_NULL)
			),
			'id'
		);
	}
 
	function down(){
		echo 'version down to 0';
		echo 'drop table profiles';
		drop_table('profiles');
		echo 'done!';
	}
}
?>

マイグレーションを実行するには、ブラウザでmigrateコントローラのinstallメソッドを呼び出します。

http://www.myapp.com/ci_path/migrate/install

installメソッドは最新のマイグレーションまでマイグレーションを実行します。
また、versionメソッドを使えば、任意のバージョンにすることもできます。

http://www.myapp.com/ci_path/migrate/version/1
9 月 10

とりあえずサンプル

とりあえず、サンプルを書いて動作を観察してみようと思う。

このサンプルはyahooのトップページを読み込んでキーワード“ruby”を検索して結果をputsで表示します。

require 'kconv'
require 'mechanize'

$KCODE = 'UTF8'
url = "http://yahoo.co.jp"
agent = WWW::Mechanize.new
agent.user_agent_alias = 'Windows IE 7'
page = agent.get(url);
form = page.forms.with.name('sf1')
form.fields.name('p').value = "ruby"
form.fields.name("search.x").value = 1
form.fields.name("fr").value = "top_ga1"
form.fields.name('tid').value = "top_ga1"
form.fields.name('ei').value = "UTF-8"

result = form.submit()

puts result.body.tosjis

user_agent_aliasでユーザエージェントを設定

ユーザエージェントを指定する場合は、あらかじめ用意されているuser_agent_aliasを使うと、長ったらしいユーザエージェントを覚えておく必要なく便利です。

アイリアスは以下のものが用意されています。

  • Windows IE 6
  • Windows IE 7
  • Windows Mozilla
  • Mac Safari
  • Mac FireFox
  • Mac Mozilla
  • Linux Mozilla
  • Linux Konqueror
  • iPhone
  • Mechanize

Getメソッドでページを読み込む

WWW:Mechanize.get()でGETメソッドでページをリクエスト。戻り値としてWWW::Mechanize::Pageインスタンスが得られます。

HTTPレスポンスのステータスコードがが200、301、302以外の場合には、Mechinizeは例外ResponseCodeErrorが送出します。例外を処理する場合は以下のような感じかなっと。

require 'mechanize'

url = "http://www.matzmtok.com/not_exits.html"
agent = WWW::Mechanize.new
begin
  page = agent.get(url);
rescue TimeoutError => ex
  puts "time out!#{ex.message}"
rescue WWW::Mechanize::ResponseCodeError => ex
  #レスポンスコードが200,301,302以外ならResponseCodeErrorがraise
  case ex.response_code
  when "404"
    puts "Response Code #{ex.response_code}: ページが見つかりません。"
  else
    warn ex.message
  end
rescue
  puts $@
else
  puts page.body
end

フォームにアクセスする

pageを得たら、page内のフォーム要素にアクセスすることができます。

各フォームはWWW:Mechainze::Formで表され、ページ内のフォームはfields属性を通じてアクセスできます。(fields属性はWWW::Mechanize::Listインスタンス)。フォームの各フィールド(inputなど)には、フィールドに割り当てられたname属性の値を使用してアクセスします。以下の例はname属性に"p"をもつフィールドに"ruby"という文字列を設定しています。

form.fields.name('p').value = "ruby"

ところで、WWW::Mechanize::Listにはwith、andメソッドが定義されています。

このメソッドはコードを直観的で読みやすくするためのもので、メソッド自体はselfを返すだけでコードの働きには影響を及ぼしません。

こういうのをシンタックスシュガーというらしくPerlなんかではよく用いられるものらしいです。まぁ、英語をネイティブで話すわけでもないので自分にはあんまりピンとこないですが覚えておくといいかもしれません。

with、andを使うと以下のような記述ができます。

form.fields.with.name('foo').and.href('bar.html')

フォームを送信する

Formのデータの送信は、Form#submit()もしくはMechanize#submit()メソッドにより行う。

submitメソッドの引数にbuttonを指定するとフォームの送信のためにクリックされたボタンを送信するクエリーに追加します。

9 月 06

先日公開されたGoogle Chromeをインストールしてみたんだけど、Googleのアナウンスにあったとおりの”軽い・早い”です。だいたい、早いとかって条件付きだったりするんですが、Chromeは違いました。さすがGoogleといったところでしょうか。

もともとWebKitのレンダリングエンジンが優れているというのもあるのだろうけど、V8とよばれるJavascriptエンジンの出来がかなりいいみたいです。これから色々なベンチマークによる比較が行われるだろうけど、どんな結果になるのか楽しみにしてます。

ところで、この記事はGoogle Docsから投稿しているんです。Google DocsならGoogle Gearに対応して、オフラインで記事を編集することも可能だし、Docs側からブログにパブリッシュできるようなのでブログエディタとしてDocsを使ってみるのもありかなっと。

ただ、Docsが書き出すHTMLが汚いこと汚いこと。すべての要素にid属性が割り当てられてたりするし、もうちょっとなんとかならんもんかなぁっと。


9 月 01

SWFAddressとは

SWFAddressはFlashやAjaxを使ったダイナミックなサイトにディープリンク機能を提供するライブラリです。Flex3ならHistoryManager/BrowserManagerクラスを使えば機能を実現することができますが、SWFAddressはディープリンクの機能に加えてトラッカーファンクションを指定できるなど、ちょっと便利な機能があります。

使ってみよう

  1. 対応ブラウザSWFAddressは以下のブラウザに対応しています。
    • Iinternet Explorer6+
    • Firefox 1+
    • Safari 1.3+
    • Opera 9.02+
    • Camino 1+
    • Mozilla 1.8+
    • Netscape 8+
  2. ライブラリの読み込みとオプション

    SWFAddressはjavascriptのライブラリの読み込み時にオプションをsrc属性のurl中にクエリーとして渡します。オプションとして使用できる値は以下の4つがあります。

    • history (bool値) デフォルトはtrue
    • html (bool値) デフォルトはfalse
    • strict (bool値) デフォルトはtrue
    • tracker (文字列) デフォルトは_trackDefault
    1. <script href="swfaddress.js?history=1&html=1&strict=0&tracker=customTracker" type="text/javascript"></script>
    2.  

    history : ブラウザに履歴を残すかどうか

    html : IEでのみ効果がある。フラグメントの値を保存する方法が異なる。falseだとiframe内にjavascriptを書き出して値を保持するが、trueの場合は、iframe要素のsrc属性値として値を保持する。この違いがどういう意味があるのか不明。わかる方教えて~

    strict : URLにスラッシュを入れるかどうか。 フラグメントの#の後にスラッシュが入ります。

    tracker : トラッカーファンクションを指定する。URLが更新されるたびに呼ばれる。
    デフォルトのトラッカーファンクションはurchinTrackerとpageTracker._trackPageviewを呼び出すようになっているので、Google Analyticsを利用している人は、何もしなくてもその恩恵が受けられます。

    SWFAddressは、SWFObject、FlashObject、UFO、AC_FL_RunContentなどと組み合わせて使う場合に、それらのメソッドの一部を書き換えるので、SWFAddressよりも先に、それらのライブラリを読み込ませておいた方がよいでしょう。

  3. サンプル

    サンプル

    ヘッダー部分 ライブラリの読み込みと、タグの書き出し

    <script src="js/swfobject.js" type="text/javascript"></script>
    <script src="js/swfaddress.js?strict=0" type="text/javascript"></script>
    <script type="text/javascript">
         var flashvars = {};
         var params = {
              menu: "true",
              scale: "noScale",
              allowScriptAccess: "sameDomain"
          };
          var attr = {
               id: "altContent",
               name:"altContent"
          }
          swfobject.embedSWF("SWFAddress.swf", "altContent", "100%", "100%", "9.0.0", "expressInstall.swf", flashvars, params, attr);
    </script>

    このサンプルでは、swfobjectと組み合わせてswfaddressを使用しています。

    ここで大事なのは、SWFAddressよりもSWFObjectのライブラリを先に読み込んでやることと、embedSWF()メソッドに渡す8番目の引数attrにSWFを表示するhtml要素のタグのidを指定してやることです。

    body部分

     
    <ul>
    <li><a href="" onclick="SWFAddress.setValue('ClickA'); this.blur(); return false;" rel="ClickA">ClcikA</a> </li>
    <li><a href="" onclick="SWFAddress.setValue('ClickB'); this.blur(); return false;" rel="ClickB">ClcikB</a></li>
    </ul>
    <div id="altContent">
    <h1>SWFAddress</h1>
     
    Alternative content
     
    <a href="http://www.adobe.com/go/getflashplayer"><img
          src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /></a>
    </div>
     

    SWFAddress.setValue()メソッドを呼ぶことでURLを更新してやります。URLが更新されると、自動的にSWF側にもSWFAddressEvent.CHANGEのイベントが発行されます。

    as側の記述もjavascript側とほとんど同じように記述できる。

    <?xml version="1.0"?>
      <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="init(event)">
          <mx:Script>
          <![CDATA[
          import mx.events.FlexEvent;
          import com.asual.swfaddress.SWFAddress;
          import com.asual.swfaddress.SWFAddressEvent;
          import mx.controls.Alert;
          import mx.events.ItemClickEvent;
     
          public function init(event:FlexEvent):void{     SWFAddress.addEventListener(SWFAddressEvent.CHANGE, swfAddressChangeEvent);
          }
     
          public function swfAddressChangeEvent(event:SWFAddressEvent):void {
             var path:String = event.path;
             Alert.show(path, "path");
          }
     
          public function itemClickHandler(event:ItemClickEvent):void {
              var index:String = String(event.item);
              SWFAddress.setValue(index);
          }
          ]]>
          </mx:Script>
          <mx:ButtonBar itemClick="itemClickHandler(event);">
         <mx:dataProvider>
            <mx:String>Menu_1</mx:String>
            <mx:String>Menu_2</mx:String>
            <mx:String>Menu_3</mx:String>
         </mx:dataProvider>
          </mx:ButtonBar>
       </mx:Application>