ホーム ブログ ページ 54

SWFバイナリ解析

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは。エンジニアの はる です。
今回はちょっとだけマニアックにswfのバイナリ解析方法について書いてみたいと思います。

バイナリって何となく苦手意識のある方も多いかと思いますが、swfのバイナリ解析は数パターンの計算方法だけ抑えてしまえば意外と素直に読み解けてしまいます。

文系大学出身&英語が読めない私でも大丈夫だったので、興味のある方は是非!

用意するもの

バイナリエディタ

まずはバイナリファイルを閲覧・編集できる環境を用意します。

バイナリエディタは様々なものがありますが、自分の環境や好みに合わせて選んでみてください。代表的なものを下記します。

1. バイナリモードでswfファイルを開く
vi -b hoge.swf
2. テキスト形式から16進数ダンプ形式に変換
:%!xxd
3. 16進数ダンプ形式からテキスト形式に変換
:%!xxd -r

解析用SWF

解析するためにはSWFファイルが必要です。適当にSWFをパブリッシュして用意しておきましょう。
※今回の記事では、非圧縮のSWFをもとに解説させていただきます。解析用SWFは、「ファイル」→「パブリッシュ設定」→「SWF設定:ムービーの圧縮」のチェックをはずしたものをパブリッシュしましょう。

基数変換の基礎知識

解析中に2進数・10進数・16進数の相互変換が沢山あります。このあたりの計算方法は事前に覚えておくか、基数変換を簡単に行えるWEBサイトやソフトが沢山ありますので、用意しておきましょう。

以上の用意ができたら、解析用SWFをお好きなバイナリエディタで開けば、準備完了です。

SWFフォーマット

SWFのバイナリは下記のような形式で構成されています。
ヘッダ(header、header_movie)に基本情報が格納されており、コンテンツ(tag block)にシェイプやスクリプト等のコンテンツ情報が順々に格納されています。

SWF
header(8バイト)header_movie(可変長)tag block(可変長)tag block(可変長)..end tag
圧縮有無(3バイト)SWFのバージョン(1バイト)ファイルサイズ(4バイト)ステージの幅・高さ(可変長)フレームレート(2バイト)_rootのフレーム数(2バイト)TLC構造(可変長)TLC構造(可変長)2バイト

ヘッダ

まずはヘッダから読み解いていきましょう。ヘッダから読み取れる情報は以下の6種類です。

ヘッダから読み取れる情報

  1. 圧縮されているかどうか
  2. Flash Player のバージョン
  3. ファイルサイズ
  4. ステージの幅・高さ
  5. fps(frame per second)
  6. _rootのフレーム数

上記の情報が格納されているヘッダの構成は以下のようになっています。

ヘッダのバイナリフォーマット

ヘッダ
header(8バイト)header_movie(可変長)
圧縮有無(3バイト)SWFのバージョン(1バイト)ファイルサイズ(4バイト)ステージの幅・高さ(可変長)フレームレート(2バイト)_rootのフレーム数(2バイト)

それでは実際にバイナリを読み解いていきましょう。1バイト目から順に解説していきます。

バイナリ詳解

1. 1〜3バイト(長さ:3バイト)

SWFが圧縮されたものがどうかを判別します。

16進数説明備考
46 57 53圧縮されていないSWFFWS
43 57 53圧縮されたSWF(※SWF6以降のみ)CWS

CWSの場合、8バイト以降がZLIB圧縮されていることを示しています。この場合、8バイト以降をZLIB展開する必要があります。
※以降の解説はFWSの前提ですすめさせていただきます。

2. 4バイト目(長さ:1バイト)

SWFのバージョンが格納されています。以下の例の場合、SWF7形式であることが分かります。

16進数説明備考
07SWFのバージョン

3. 5〜8バイト(長さ:4バイト)

swfファイルのファイルサイズが格納されています。ただし、数値はリトルエンディアン形式で格納されているため、注意が必要です。

16進数説明備考
ec 52 01 00ファイルサイズリトルエンディアン形式

<計算方法>1. バイトスワップ

ec 52 01 00
逆順00 01 52 ec

2. 10進数に変換

1.の結果(16進数)000152ec
10進数86764

3. ファイルサイズ = 86764 byte

4. 9バイト〜(長さ:可変長)

ステージの幅・高さが格納されています。ただし、数値はRECT構造体で表現されているのに加え、単位はTWIPSのため、注意が必要です。

16進数説明備考
70 00 09 60 00 00 96 00 00 …ステージの幅・高さRECT構造体で表現。単位はTWIPS(トゥイップ)

<計算方法>1. 2進数に変換

16進数70 00 09 60 00 00 96 00
2進数01110000 00000000 00001001 01100000 00000000 00000000 10010110 00000000 00000000

2. 最初の5ビットを10進数に変換

2進数01110
10進数14

3. 2.の結果毎に6ビット目以降を区切る(余りのビットは捨てられます)

最初の5ビット14ビット14ビット14ビット14ビット余りのビット
01110000 00000000 00001001 01100000 00000000 00000000 10010110 00000000

4. 3.の結果を10進数に変換

X座標の最小値X座標の最大値Y座標の最小値Y座標の最大値余りのビット
16進数00000000000000010010110000000000000000000001001011000000000
10進数0480004800

5. 4.の結果がtwipのため、ptに変換(1pt=20twips)

X座標の最小値X座標の最大値Y座標の最小値Y座標の最大値
twips0480004800
pt02400240

6. 縦幅 = 240px, 横幅 = 240px

5. 長さ:2バイト

フレームレートが格納されています。

16進数説明備考
00 08フレームレート逆順にした結果の1ビット目が整数部、2ビット目が小数部

<計算方法>1. バイトスワップ

00 08
逆順08 00

2. 整数部、小数部を10進数に変換

1.の結果(16進数)08.00
10進数8.0

3. フレームレート = 8 fps

6. 長さ:2バイト

_root(メインタイムライン)のフレーム数が格納されています。

16進数説明備考
14 00_rootのフレーム数リトルエンディアン形式

<計算方法>1. バイトスワップ

14 00
逆順00 14

2. 10進数に変換

1.の結果(16進数)00 14
10進数20

3. _rootのフレーム数 = 20

ヘッダの解析はこれで終了です。SWF解析の肝は次バイトからのコンテンツ(tag block)ですが、これはヘッダ解析で行った計算方法の応用です。

ここから先は、tagの種類毎にtag blockの構造が異なるため、Adobe社が配布している『SWF file format specification』を片手にtagの種類を特定し、それに合った計算方法で解析していくことになります。

今回は一例として、ヘッダに続くtab block(SetBackgroundColor)の解説を行います。

コンテンツ

コンテンツのバイナリフォーマット

  • コンテンツは tag block毎に続き、tag blockは[ tag | length | contents ]の構造(以下、TLC構造)となる。
  • tag block の構造は2種類存在し、”「length < 3f」の場合”と”「length >= 3f」または特殊形式の場合”で異なる。

1. タグ形式1(※「length < 3f」)の場合

コンテンツ
tag block(可変長)
record header(2バイト)contents(lengthバイト)
tag(10ビット)length(6ビット)contents(lengthバイト)

2. タグ形式2(※「length >= 3f」または特殊形式)の場合

コンテンツ
tag block(可変長)
record header(6バイト)contents(lengthバイト)
tag(10ビット)3f(6ビット)length(4バイト)contents(lengthバイト)

バイナリ詳解

16進数43 02 ff ff ff ff 0a 05 00 00 00 69 6e 69 74 00 3f 03 3a 03 00 00 88 06 01 1e 00 00 …
2進数01000011 00000010 11111111 11111111 11111111 11111111 …

1〜2バイト(長さ:2バイト)

16進数説明備考
43 02tag(10ビット)+length(6ビット)リトルエンディアン形式

<解析方法>1. バイトスワップ

43 02
逆順02 43

2. 2進数に変換

16進数02 43
2進数00000010 01000011

3. 10ビット、6ビットに分割

taglength
0000001001000011

4. 『SWF file format specification』のp.271〜p.273をもとにtagの種類とlengthを算出する

2進数10進数結果
タグ種別00000010019SetBackgroundColor
length00001133

5. タグ形式2(※「length >= 3f」または特殊形式)の場合、続く4ビットを取得し、lengthを算出する6. 続くlengthバイトを取得し、『SWF file format specification』の各タグ処理の記載に沿って解析
※SetBackgroundColorの場合

RGBff ff ff

※setBackgroundColorは、必ずコンテンツの先頭に位置し、「長さ:5バイト」となる

setBackgroundColor(5バイト)
tag(1バイト)length(1バイト)RGB(3バイト)

あとはtagの種類毎に決められたフォーマットで解析していき、順々にコンテンツ(tag block)を解析していきます。
このswfのバイナリ構造を抑えておくと、例えば、Flash単体では通常取得することが困難な情報をバイナリレベルでswf内に埋め込んでから出力するアプリケーション開発したり、swfmillで出力されるxmlフォーマットもバイナリ構造と近いので理解しやすくなったりします。 皆さんもちょっとアブノーマルなバイナリの世界にいかがでしょうか?

tritonn(MYSQL+Senna)で全文検索。

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

MYSQLで高速な日本語全文探索を実現するため、tritonnでMYSQL+Sennaにする。
■tritonnとは?
MySQLから全文検索エンジンSennaを利用可能にするための改造を行うプロジェクトのこと。

■Sennaってなんなの?
Sennaは組み込み型の全文検索エンジン。

■Sennaの特徴
 ・組込み型
  MySQLに組み込んで使用する為、MySQLがアプリケーションからSennaを隠蔽します。
  開発者の方はSQL文を操作するだけで、全文検索を実行できます。
 ・高速
  キーワードがテキスト内のどの位置にあるかという位置情報を持った、
  完全転置インデックスを採用している為、フレーズ検索においても高速な検索が可能です。
 ・即時検索
  作成したインデックスに対して、インクリメンタルに文書の追加/変更/削除の操作を加えることができるため、
  文書に次々と変更が加えられる場合にも、一度作成したインデックスを作成し直す必要がありません。

■なんでSennaをいれる必要あるの?
1. MySQLには日本語環境で使用するための十分な全文検索機能ができない。
 ・MySQLのFULLTEXTインデックスのキーワード抽出を行うパーサが、
 「半角スペースで区切られているものをキーワードとして認識する」という実装になっているため。
   ・LIKE演算子による部分一致検索は可能だが、インデックスを一切使用することができなくなってしまうため、
  テーブルのフルスキャンが発生します。
2. MYSQLでは全文検索を行うための実装面での十分な高速化が行われていない。

■toritonn 導入について
toritonnのダウンロードについて
http://qwik.jp/tritonn/download.html
toritonnのユーザガイド
http://qwik.jp/tritonn/userguide.html

■tritonnの2ind機能(2インデックス同時使用機能)
MySQLではクエリを実行する際、1つのテーブルに対してFULLTEXTインデックスと他のインデックスを組み合わせて利用することができません。
従ってFULLTEXTインデックスを用いる用いた場合、以下のような問題が生じます。

1. limit指定で出力を制限しても応答が遅い問題
2. count(*)等で件数を取得するだけでも応答が遅い問題
3. 全文検索以外の条件で絞り込む処理が遅い問題
4. 全文検索以外の条件でソートする処理が遅い問題

上記の問題を解決するために、MySQLが全文検索用のインデックスと通常のインデックスの両方を併用できるようにするのが2ind機能です。

■2ind利用法
Tritonnパッチをあてると、MySQLのサーバ変数(SESSIONスコープ)に”senna_2ind”という変数が追加されます。

下記のどちらかの方法で、2ind機能の利用を動的にOn/Offすることができます。

・my.cnfあるいはmysqldの起動オプションで”–senna-2ind”を指定する。
・SETコマンドでONを設定してください。→ mysql> SET SESSION senna_2ind=ON;

以下、公式より2ind機能使用について、注意書きがあります。

注意:2ind機能はまだ安定度合いとしてはβ段階にあります。
   実際に利用する前に有効かどうか、利用環境で問題は無いかどうかをご確認の上、使用してください。

■参考文献

■Senna 組み込み型全文検索エンジン
http://qwik.jp/senna/FrontPageJ.html
■toritonnプロジェクト
http://qwik.jp/tritonn/
■toritonn ユーザガイド
http://qwik.jp/tritonn/userguide.html
■MySQLで全文検索 – FULLTEXTインデックスの基礎知識
http://www.tatamilab.jp/rnd/archives/000389.html

場所を覚えられない人必見!?「ここメモ!」を試しました

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

今回は、場所を覚えられない人必見の「ここメモ!」を試してみました。

自分も一度行ったところを忘れてしまうタイプなので、周辺情報など記録しておくと便利!?

起動すると、GPSが動きますが現在地を地図上に表示してくれるので、 後はタイトルと写真を写して保存する。いちいち場所入力せずに済むのでとても便利です!!

ここメモ!情報詳細画像

録音機能もあるので、音声でコメントなんか吹き込んでおいたり、特徴的な音を録音しておくと更に分かりやすいかも・・・

ここメモ!一覧

登録した場所なんかは、登録日を軸として日別/週別/月別にリストで表示されます。

ここメモ!一覧(週別)

まだ使いこなせてないのですが、登録日を軸として表示ではなく、全てのリストを表示して欲しいなと思いました。

ここメモ!地図から検索

地図からのピン検索などユニークな検索もあったので、面白いです。

色々な場面での話題にもなりそうなので重宝しそうです。

iTunesのリンク貼っておきます!!こちらからどうぞ

ここメモ!

Ruby1.9でRailsる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Ruby1.9 で Rails2.3 を動かす方法を紹介します。

 今回の環境

  1. Ruby1.9.1p376
  2. Rails2.3.5

 はじめに

Ruby1.9 を Rails で動かす上で、一番頭を悩ませる問題が、

incompatible character encodings: ASCII-8BIT and UTF-8

というエラーかと思います。

Ruby1.9 から String に Encoding を持つようになりました。

これにより異なる Encoding 同士では、比較・結合を行うことができず、上記のようなエラーが発生してしまいます。

これは、Magic Comment で script encoding をUTF-8で指定しても、DB の Encode を UTF-8 に指定しても発生してしまいます。

これには色々と原因があるのですが、大きくは ActionView のレンダリング時に、ASCII-8BIT で出力される部分があるのが問題となります。

通常、Ruby1.9 でコーディングする際には、Magic Comment を記述し、script encoding を明示します。

この方法だけで、基本的な1ファイルでの View のレンダリングは問題なく行えます。

しかし、partial や layout を利用して、複数の View を組み合わせた場合に、問題が発生します。

ActionView は、内部的に読み込んだ View ファイルを buffer に格納し、順次結合していきますが、

buffer の Encoding が、partial した View と結合される際に、ASCII-8BIT となってしまうため、エラーが発生するようです。

 ActionView にパッチを当てる

ということで、ActionView にパッチを当てます。

View ファイルを読み込み、順次結合してる部分に問題があるので、その部分を UTF-8 に Encode するように指定します。

パッチを当てる方法は何でもいいのですが、今回は alias_method_chain を使った方法で記述します。

下記コードをlib以下などに格納して、起動時に読み込まれるようにします。

action_view/renderable.rb

# -*- coding:utf-8 -*-
module ActionView
  module Renderable
    private
    def compile_with_magic_comment!(render_symbol, local_assigns)
      locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join

      source = <<-end_src
        # -*- coding:utf-8 -*-
        def #{render_symbol}(local_assigns)
          old_output_buffer = output_buffer;#{locals_code};#{compiled_source}
        ensure
          self.output_buffer = old_output_buffer
        end
      end_src

      begin
        ActionView::Base::CompiledTemplates.module_eval(source, filename, 0)
      rescue Errno::ENOENT => e
        raise e # Missing template file, re-raise for Base to rescue
      rescue Exception => e # errors from template code
        if logger = defined?(ActionController) && Base.logger
          logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}"
          logger.debug "Function body: #{source}"
          logger.debug "Backtrace: #{e.backtrace.join("\n")}"
        end

        raise ActionView::TemplateError.new(self, {}, e)
      end
    end
    alias_method_chain :compile!, :magic_comment
  end
end

actionpack/lib/action_view/renderable.rb の68,69行目に Magic Comment を追加するだけです。

このパッチだけで、レンダリング時の Encoding の問題はほぼ解決できるのですが、

次に問題になってくるのが、日本語の文字列をフォームから GET で送信した場合です。

この場合は、

incompatible character encodings: UTF-8 and ASCII-8BIT

というエラーが発生します。

これは、送信されたデータを text_field などに再セットする場合に発生します。

UTF-8 の view に対して、ASCII-8BIT の文字列を結合しようとしているのがエラーの原因となります。

どうやらこの問題は Rack::Utils.unescape にあるようです。

Rack は、URL をデコードする際に、マルチバイト文字列を ASCII-8BIT として Encode してしまいます。

POST も同様に Form データの parse 時に同様の問題が発生しますが、GET と同じ対応で対処することは可能です。

ただし、POST の場合はこれだけでは解決せず、multipart 指定のフォームについては別途対処する必要があります。

multipart 用の parser である、Rack::Utils::Multipart.parse_multipart の戻り値の文字列は ASCII-8BIT となってしまうので、こちらも別途対処する必要があります。

上記の問題は、Rack にパッチを当てることで解決することは可能です。

ただし、この挙動自体は Rack としては正しい動作ではあります。

Encoding の問題はアプリケーション側で解決すべきで、Rackは関知すべきではないからです。

 ActionControllerにパッチを当てる

ということで、上記の問題を解決するパッチを ActionController に当てます。

パッチの適用方法は ActionView と同様です。

action_controller/request.rb

# -*- coding:utf-8 -*-
module ActionController
  class Request
    private
    def normalize_parameters_with_force_encoding(value)
      (_value = normalize_parameters_without_force_encoding(value)).respond_to?(:force_encoding) ? 
         _value.force_encoding(Encoding::UTF_8) : _value
    end
    alias_method_chain :normalize_parameters, :force_encoding
  end
end

actionpack/lib/action_controller/request.rb の472行目の normalize_parameters を修正します。

これにより、アプリケーションのコントローラでparamsを利用する時点で、リクエストパラメータに含まれる文字列は、UTF-8 に Encode されているので、view との結合も問題なく行うことができます。

 最後に

今回紹介した ActionView、ActionController を修正する方法で、Ruby1.9 上で Rails2.3 系をとりあえず動かすことはできそうです。

ただし、利用するプラグインやAPサーバによっては、別途対応が必要な場合があるようです。

SourceForge.JP のプロジェクトを Subversion から Git へ移行する

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


たろちゃんです。SourceForge.JP のプロジェクトをSubversionからGitへ移行しましたのでその時に記録から手順を紹介します。

僕が現在開発しているエレコマSourceForge.JPにてホスティングを行っており、開発リソースをすべて一元管理しております。

開発当初、弊社内のレポジトリがSubversionであった事や僕自身にSubversionの運用ノウハウがあったことからSourceForge.JPでもSubversionを採用していました。

しかしながら、Railsのプロジェクトという特性からか、何人かgit-svnを利用している方がおり、また開発に集中するという意味でもGitの方が魅力的であるため、今回SubversionからGitへ移行しました。

まずはGit自体を有効にします。Gitを有効にするためにはSourceForge.JPへ管理者としてログインしてからプロジェクトのメニューの「管理」から「プロジェクト情報変更」を選択して「プロジェクト情報変更画面」を出します。この中にある「利用する機能」にあるGitを利用のチェックボックスを有効にして情報を保存します。

続いてプロジェクトのメニューの「ソースコード」から「Git管理」を選択して、「新規Gitレポジトリを作成」というリンクを辿り、必要な情報を入力をしてGitレポジトリを作成します。数分のち、作成済が解除され、レポジトリが有効になります。

エレコマの場合は以下の内容で生成されました。

git clone tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git

では、さっそく移行作業を開始しましょう。

まずは作業用ディレクトリを作成し、移動します。

$ mkdir -p ~/develop/elecoma/tomove_git
$ cd ~/develop/elecoma/tomove_git

次にgit svn cloneを使ってSubversionのデータをすべて取得します。–prefixには svn/ を追加してリモートレポジトリをわかりやすくします。

$ git svn clone --prefix svn/ -s svn+ssh://tmatsuzawa@svn.sourceforge.jp/svnroot/elecoma/

そしてgit remote add を使ってSourceForge.jpのGitレポジトリを追加します。リモートレポジトリの名前はoriginとします。

$ cd elecoma
$ git remote add origin tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git

あとはmasterの内容をリモートレポジトリ(origin)へ push します。念のため –dry-run をつけて内容を確認しておくとよいでしょう。

$ git push origin master --dry-run
(初回実行時にgit.sourceforge.jpをsshのknown_listへ追加するか確認されます)
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new branch]      master -> master
$ git push origin master
Counting objects: 2581, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2504/2504), done.
Writing objects: 100% (2581/2581), 6.94 MiB | 2.83 MiB/s, done.
Total 2581 (delta 1006), reused 0 (delta 0)
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new branch]      master -> master

以上で、masterの内容をリモートレポジトリへすべて push しました。しかし、エレコマではリリースごとに tag を切っていますので、こちらも反映します。

Subversionでの tag はGitにおいては branch に相当します。まずはリモートの branch を確認します。

$ git branch -r
  origin/master
  svn/1.0.x
  svn/tags/1.0.6
  svn/tags/1.0.7
  svn/tags/release-1.0.1
  svn/tags/release-1.0.2
  svn/tags/release-1.0.3
  svn/tags/release-1.0.4
  svn/tags/release-1.0.5
  svn/trunk

この場合は svn/tags/release-1.0.1 という branch がGitにおける tag に相当します。なので、branch に移動して tag を切ってそれを push していきます。

$ git checkout svn/tags/release-1.0.1
$ git tag 1.0.1
$ git push --tags origin --dry-run
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new tag]         1.0.1 -> 1.0.1
$ git push --tags origin
Counting objects: 1, done.
Writing objects: 100% (1/1), 266 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new tag]         1.0.1 -> 1.0.1

これで tag が切れました。他の tag も同様に作業をしていきます。

$ git checkout svn/tags/release-1.0.2
$ git tag 1.0.2
$ git push --tags origin
Counting objects: 2, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 454 bytes, done.
Total 2 (delta 1), reused 0 (delta 0)
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new tag]         1.0.2 -> 1.0.2

.
.
.

$ git checkout svn/tags/1.0.7
$ git tag 1.0.7
$ git push --tags origin
Counting objects: 1, done.
Writing objects: 100% (1/1), 259 bytes, done.
Total 1 (delta 0), reused 0 (delta 0)
To tmatsuzawa@git.sourceforge.jp:/gitroot/elecoma/elecoma.git
 * [new tag]         1.0.7 -> 1.0.7

以上ですべての tag が切れました。

あとは、別のディレクトリで git clone をして確認をしましょう。

なお、エレコマでは 1.0.x という branch も存在していましたが、 git ではリリースされたものに対しては branch を用意する必要がないためこのままにしておきます。必要に応じて、以下のようにして branch を作成して作業をするとよいでしょう。

$ git branch RB_1.0.7_1 1.0.7

なお、このテクニック自体はSourceForge.JP以外でも使えると思います。SubversionからGitに移行を検討している方はぜひ参考にしてください。

さて、これで2.x系の開発が…はかどるといいなぁ…

WEBサーバ2台構成でpoundによるLBを行う例

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


WEBサーバ2台構成でpoundによるLBを行う例を書きます。

 WEBサーバ2台構成でpoundによるLBを行う例を書きます。

サーバの情報など

web-vip : 10.0.0.10

web1 : 10.0.0.11

web2 : 10.0.0.12

今回の例ではpoundをweb1に設置し、負荷分散先をweb1、web2とします。

設定方法としては、poundでport80をListenし、web1のport8000、web2のport80へLBする。

という感じです。

poundの設定です

ListenHTTP

Address 10.0.0.10

Port 80

Service

BackEnd

Address 10.0.0.11

Port 8000

End

BackEnd

Address 10.0.0.12

Port 80

End

End

End

以上の設定で完了です。

必要に応じて負荷分散の割合を変更することができますので、

様子をみてweb1の分散を減らしてあげる必要があるかもしれません。

また、インストールに関しては、下記エントリーが大変参考になりました。

http://doruby.kbmj.com/curi/20100313/Pound_HTTP_2048kb_

あと、poundをweb1のみに設置している理由はListenのAddressの重複が許されていないが為です

なのでpoundが落ちた場合などはweb2へvipをうつしますので、web2は80でlistenしてます

mod_rpafを使ったIPアドレスのアクセス制限

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

mod_rpafを使ったIPアドレスのアクセス制限に悩まされたので紹介します。

リバースプロキシ(pound)などを使った環境でapacheのアクセスログに

出力されるIPアドレスがプロキシサーバのIPになる問題を解決するために利用されるmod_rpafですが、

ログの問題はクリアしたところで、IPアドレスを使ったアクセス制限が機能しない問題に悩まされました。

今回利用したmod_rpafのバージョンは0.6です。

apache2.2.xの環境で試しています。

インストール方法は割愛です。

インストール後に以下のような設定ファイルを記述します。

LoadModule rpaf_module modules/mod_rpaf-2.0.so
RPAFenable On
RPAFsethostname Off
RPAFproxy_ips <プロキシサーバのIPアドレス>

「Allow from」にIPアドレスを記載したアクセス制限は機能しなかったのでRewriteCondを使った制限に書き換えました。

Order deny,allow
Deny from all
Allow from xxx.xxx.xxx.xxx

RewriteCond %{REMOTE_ADDR} !^XXX\.XXX\.XXX\.XXX$
RewriteRule ^.*$ - [F,L]

mod_rpafのソースまでは見ていないですが気付き難いですね。

当然mod_authとmod_rewriteで扱う変数が違うのでしょう。

Ruby On Rails 正規表現で改行を探す

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ども、 rick No.17です。
今回は正規表現で以外とはまりがちというか忘れがちなことについて書きます。Railsのvalidateの一つである
validates_format_ofを使用している時にちょっとはまりました。

前提

こんな記事があるとします
hoge
hogehoge

しかし、開発者は改行を付けてほしくない。
なので改行をした記事は、投稿できない様にしたいわけです。

問題

$vi app/models/hoge.rb
class Hoge < ActiveRecord::Base
validates_format_of :body, :with => /^[^\r\n]*$/, :message => “は、改行禁止です”
end

記事のカラムはbodyカラムで、改行禁止の正規表現を書いたつもりでした。。が、、、、
全然問題なく改行しても登録されてしまう。。。

なぜか???

解説

答えは^と$でした。
^は行頭、$は行末であり文の先頭から最後ではない。
つまり改行前の「hoge」と改行後の「hogehoge」しか見てくれない為、
改行の\nはマッチしない。
hoge\n
hogehoge

上記の下線部分しかみてくれないわけですね。

解答

では、どうすればいいか?
\Aと\zを使用すればいいのです。
\A\zは改行を無視して文頭、文末を見てくれるので一文の中から改行の有無を探してくれます。
$vi app/models/hoge.rb
class Hoge < ActiveRecord::Base
 validates_format_of :body, :with => /\A[^\r\n]*\z/, :message => “は、改行禁止です”
end

あぁそういえばそうだね。といったかんじですが、
なにげに忘れてしまいがちなので覚えておきましょう。

undefined method `fdiv’ for 0:Fixnum

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

急にタイトル通りのエラーが出てしまいました。

ruby-1.8.6にてgemいじってたら

% gem install rails -v=1.2.6




  WARNING:  RubyGems 1.2+ index not found for:


RubyGems will revert to legacy indexes degrading performance.
        Updating metadata for 1 gems from http://gems.rubyforge.org/
           .
complete
                                                                                                ^U
Bulk updating Gem source index for: http://gems.github.com/
Successfully installed activesupport-1.4.4
Successfully installed activerecord-1.15.6
Successfully installed actionpack-1.13.6
Successfully installed actionmailer-1.3.6
Successfully installed actionwebservice-1.2.6
Successfully installed rails-1.2.6
6 gems installed
Installing ri documentation for activesupport-1.4.4...
ERROR:  While executing gem ... (NoMethodError)
    undefined method `fdiv' for 0:Fixnum

な事をいわれてしまいました。インストール自体はできている模様なので問題なさそうですが、ドキュメントのインストールでこけてます。たぶん。その場しのぎの回避策で~/.gemrcに

install: --no-ri --no-rdoc
update: --no-ri --no-rdoc

とか書いてみました。

根本的な解決になってないですが気が向いたらまたおってみます。

WindowsXPの起動が遅いので設定を見直してみる(wuauclt.exeの確認)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

最近WindowsXPの起動が遅くて、アプリケーションの立ち上げに時間がかかります。ブラウザ立ち上げるのに5分くらいかかります。

タスクマネージャーで確認してみると、wuauclt.exeというWindowsUpdateの自動更新プロセスをなんとかしてみます。

wuauclt.exeはWindowsXP起動直後、タスクマネージャーにてプロセスをメモリ使用の大きい順でソートすると上のほうに出てきます。

このプロセスは今までのWindowsUpdateで今まで何を更新してきたかの履歴を調べ、その時点での最新のアップデートを監視するようです。なので、履歴が多ければ多いほど負荷がかかるようです。

解決方法としてはアップデート履歴を消すこと

参考URL 
http://www.inoran.com/archives/2009/0523_1753.php

↑のサイトを参考に、以下を実行します。

1. Automatic Upates(自動更新)サービスを停止
2. DateStoreフォルダの内容を削除
  ※私の場合、このフォルダの容量は200MBちょいありました。
3. AutomaticUpdates(自動更新)サービス再開

今までに比べてPC起動からブラウザ立ち上げまでの時間が少し早くなったような気がします。

画像ファイルからEXIF情報を取り出す

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

画像ファイルからEXIF情報を取り出すにはどうしたら良いのでしょうか?

まず、そんな疑問に今回は挑戦しました(大げさ)。

画像ファイルからEXIF情報を取り出すにはどうしたら良いのでしょうか?

まず、そんな疑問に今回は挑戦しました(大げさ)。

 [概要]

RubyのExifライブラリは幾つかあるようですが、現在も開発が進んでいるものが少ないようです。

そんな中、

http://rubyforge.org/projects/exifr

を見つけました。

ライセンスはRubyライセンスです。

今回、このexifrを利用して、画像ファイルからEXIF情報を取得します。

 [目的]

画像ファイルからEXIF情報、特にデジタルカメラのメーカー名、カメラ名を取得したいと思います。

(今回はSONY DSC-W300で撮影した画像を利用します)

 [方法]

[インストール]

gemからインストールしてみます。

sudo gem install exifr

インストールはとても簡単です。

gemが古い場合はインストール時にErrorが起こってしまいます。

sudo gem install rubygems-update
sudo update_rubygems

をしてgemのバージョンを最新版にするといいでしょう

[使用方法]

では、実際にirbから使ってみましょう。

tomonori:~# irb
irb(main):001:0> require 'rubygems'
irb(main):002:0> require 'exifr'
irb(main):003:0> file = '/path/to/filename'
irb(main):004:0> exif = EXIFR::JPEG.new(file)
irb(main):005:0> exif.make
=> "SONY"
irb(main):006:0> exif.model
=> "DSC-W300"
irb(main):007:0> exif.width
=> 2048
irb(main):008:0> exif.height
=> 1536
irb(main):009:0> exif.date_time
=> Sat Dec 20 16:14:57 +0900 2008

 [まとめ]

exifrというライブラリを利用することで簡単に画像ファイルからEXIF情報を取得できました。

これを利用することで、カメラ情報などが画像から取得できます。

今回は試しておりませんが、どのケータイから撮影されたものかもわかりますので、そのユーザが撮った画像であるかどうかの判断にも利用出来るかと思います。

ruby on railsでデータをCSV形式で出力する。 to_csv Plugin編

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、KBMJの中平@railsエンジニアです。

今回は、ruby on railsでデータをCSVファイル出力が簡単にできる プラグインついて書きます。

前回、ruby on railsでデータをExcelファイルに出力する方法を書きましたが

ExcelファイルよりCSVファイルに出力する機会が多いだろうと思います。

また、railsでCSVファイルを出力するのは、さまざまな方法がありますが、

今回は設定も簡単で、コードの記述も比較的少なくてすむ to_csvというpluginの使い方について書こうと思います。

環境

OS : Windows Vista
Ruby : 1.8.6
ruby on rails : 2.1 (2系以上ならto_csvが使えると思います)

to_csvのインストール

ruby script/plugin install git://github.com/arydjmal/to_csv.git

fastercsvのインストール

to_csv はfastercsvが必要なため
gem install fastercsv

コントローラーのメソッドに以下のコードを記述

def index
    @users = User.all

    respond_to do |format|
            format.html
            format.xml { render :xml => @users }
            format.csv { send_data @users.to_csv }
    end
end

@users.to_csv で取得したデータをCSV形式にしています。
また README.rdocに書いているオプションを解説すると

 @users.to_csv  
 すべてのカラムを出力

 @users.to_csv(:only => [:last_name, :role]) 
 onlyで指定したカラムを出力 

  @users.to_csv(:headers => false) 
 先頭行にカラム名を出力しない

  @users.to_csv(:except => [:last_name, :role]) 
 exceptで指定したカラム以外を出力

  @users.to_csv(:except => :role, :methods => :admin?) 
 :methodsはカラム名?で記述したカラムのデータのnullを判定しているぽい

 nullだったらfalse null以外はtrueを表示している

このような動作をするようです。
このあたりは、find時に出力データをrails側で制御してもいいかもしれません。

実際にruby on railsでデータをCSVに出力する


コントローラー名/index/hoge.csv

というようにアクション名の後ろにファイル名をつけてブラウザでアクセスすると
データがCSV形式で出力されますので、ファイルに保存後エディタ等で開くとデータを見ることができます。

なお、この出力したファイルをExcelで表示してみるとわかるのですが、
なんと日本語が文字化けします。これはto_csvの出力がUTF8になっているためで、Excelはsjisでないと日本語が文字化けしてしまいます。

原因は出力時の文字コードですので、kconvなどを利用して、CSVデータを出力している部分に tosjis をしてあげればいいでしょう。

この to_csvというplugin は簡単に導入できてコードも数行で済みますので、ruby on rails から CSV形式でデータ出力 を簡単にしたい場合には試してみてはいかがでしょうか?

Ary Djmal / to_csv plugin: Better Excel Compatibility
 http://arydjmal.com/2008/6/11/to_csv-plugin-better-excel-compatibility

arydjmal’s to_csv at master – GitHub
 http://github.com/arydjmal/to_csv

counter_cacheを使ってみよう

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは。KBMJの本多です。

今回はcounter_cacheの使い方を紹介します。
counter_cacheとは、RailsActiveRecordでRDB関連の設定の1つです。
これを設定することにより、親子関係のテーブルにおいて、親テーブルが子テーブルの
件数をキャッシュすることができるようになります。
例えばいろんな商品のクチコミサイトがあるとします。

そこでは各商品毎にクチコミを複数件登録することができ、
商品一覧ページにクチコミ件数を表示したり、クチコミ件数が多い順に並び替えたりできる、という設定です。

さて、クチコミ件数が多い順に並び替える時はどんなSQLを発行しましょうか。

商品とクチコミは1対多の関係なので、各テーブルとカラムは以下の通りです。
(今回の説明に必要ないモノは省いています)

・商品(親テーブル)
モデル名:Item
テーブル名:items
カラム:id, name

・クチコミ(子テーブル)
モデル名:Comment
テーブル名:comments
カラム:id, item_id, comment

・クチコミのモデル内での商品との紐付け設定
class Comment < ActiveRecord::Base
  belongs_to :item
end
この場合、発行されるSQLはこんな感じです。
select *,
       (select count(*) from comments where comments.item_id=items.id ) as count
from items
order by count desc;
Rails的な書き方だと
Item.find(:all,
          :select => “*,(select count(*) from comments where comments.item_id=items.id ) as count”,
          :order => “count desc”)こうなります。

動けば何でも良いのであればこれで良いのですが、このやり方だとデータ量が増えた時に処理が重くなる原因になります。
対象の商品の全クチコミをカウントするので当然「index?なにそれ?」状態です。

さすがに↑のやり方は重いですが、こういう書き方もできます。
SELECT items.id, count(comments.id) as count
FROM items
      left join comments on comments.item_id=items.id
group by items.id
order by count desc;
Rails的な書き方だと
Item.find(:all,
          :select => “items.*, count(comments.id) as count”,
          :joins => “left join comments on comments.item_id=items.id”,
          :group => “items.id”,
          :order => “count desc”)こうなります。
子テーブルをleft joinで引っ張ってきて、group化する事でカウント処理を行わないようにしました。

…SQLが目に見えて複雑化していますね。
先程の1件ごとのカウントよりはだいぶマシですが、indexが使えないので後々重くなると思います。
また、group byの指定は使用するDBがMySQLの時のみこの書き方が使用可能です。
postgresの場合はselectする項目(countを除く)をすべて指定する必要があります。かなり面倒です。
今回の例題はitemsテーブルのカラムがid, nameだけなので全部書けば良いですが、実際の開発ではこんなにカラムが少ないわけもなく、
開発現場から悲鳴が聞こえてくるハメになります…。

そこで今回紹介するcounter_cacheの出番です。
先程紹介したテーブルのカラム、及びモデルの設定を以下のように追加します。

・商品(親テーブル)
モデル名:Item
テーブル名:items
カラム:id, name, comments_count

・クチコミ(子テーブル)
モデル名:Comment
テーブル名:comments
カラム:id, item_id, comment

・クチコミのモデル内での商品との紐付け設定
class Comment < ActiveRecord::Base
  belongs_to :item, :counter_cache => true
end
itemsテーブルに追加したcomments_countは:default => 0にする必要があります。
これだけでクチコミが増えたり減ったりした時にカウントを計測してitemsテーブルのcomments_countに保存されるようになります。

先程のSQLも以下のように変化します。
select *
from items
order by comments_count desc;
Rails的な書き方だと
Item.find(:all,
          :order => “comments_count desc”)こうなります。

これならリスト取得時にクチコミ数をカウントする手間がなくなり、
またcomments_countにindexを貼ることができるため処理速度が改善されます。
是非何かの役立ててください。
それでは。

GOOD RAILS!!

PoundのHTTPヘッダーサイズ(2048kbの壁)

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

ロードバランサーによくPoundを使用しますが、うっかりしてるととんでもない落とし穴にはまります。

ある日出社すると、、、

Mさん「テスト環境でちゃんと動作する検索フォームが本番環境だと動きません。どうやらPoundがHTTPヘッダーを2048byteしか受け取れないみたいです。Apacheは4096byte受けてるのでロードバンラサーをかましてないテストではうまくいっちゃてるみたいです。」

「おはようございます」を交わす前にこんな会話からはじまるのでした。

よくよく調べると同じ症状で困ってる先人方がたくさんおりました。

つまりGETリクエストにあまりに長いクエリーを突っ込んだり、2kbを超えるようなCookieを仕込むと「長すぎるよ」って怒られるようです。

この怒られる限度が2kbか4kbの差で挙動が変わるようです。

最新のバージョンでは改善されているようですが、CentOS を使用していた関係上yum管理のものを入れておいたほうがよいだろうと rpmforge から入れた 2.4.3-1.el5.rf を使用していました。

対処法1:最新版のソースからコンパイルして入れ替える。

対処法2:新し目のrpmを探してきて入れ替える。

今回はやっぱりrpmでいれておきたいので対処法2を模索しました。

新し目のバージョンのrpm はここから拾えます。

今回はCentOS5.4なのでEPEL 5なPound-2.4.4-1.el5.x86_64.rpmで良いでしょう。

HTTPヘッダーのサイズはPoundヘッダーソースファイル内(pound.h)に

#define MAXBUF      2048

のように定義してあるのでMAXBUFが4096であることを確認する為に、Pound-2.4.4-1.el5.src.rpmも落としてソースを確認します。

2.4.4-1.el5は MAXBUF 4096 だったのでこれなら問題は起きないはずです。

rpmforgeから入れた2.4.3-1.el5.rfからはアップデートできなかったので一度削除。

# yum remove pound -y

それからダウンロードしてきた物をインストール

# yum install Pound-2.4.4-1.el5.x86_64.rpm --nogpgcheck -y

以上を入れなおした結果、Poundを経由しても問題の長いGETクエリー(4kb以下)を発行する検索フォームも動作するようになりました。

Macとwindowsでマウスの共有

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

Macもwindowsも両方使いたい!
そんなあなたに捧げます。Macとwindowsでマウスの共有ができるソフトの紹介です。

環境

Mac: MacOS 10.5
Windows: WindowsXP

インストール

synergy
こちらよりソフトのダウンロード
Macは、〜OSX.tar.gz
Winは、〜.exe
をそれぞれ落とします。

今回は、Mac側をサーバー、Win側をクライアントにする説明します。

Mac側

ダウンロードしたファイルを解凍。中にある「synergys」ファイルを
/usr/local/bin/
配下にコピーします。コマンドはこんな感じ。
# sudo cp synergys /usr/local/bin/これでソフトの設置完了。
次に設定ファイルを書きます。
解凍した中に入っているsynergy.confを編集します。
section: screens
hogemac.local: #Mac側のIPアドレスなど
hogewin:                         #Win側のIPアドレスなど
end

section: links
hogemac.local:
right = hogewin

hogewin:
left = hogemac.local
end
上記設定は向かって左側にMac、右側にWinを置くと仮定した設定です。
Mac側のIPアドレスは、
システム環境設定→共有
内の、「ローカルネットワーク上のコンピュータから、次のアドレスでこのコンピュータにアクセスできます」のあとに書いてある〜.local
がそのまま使えます。
Win側は、ローカルIPなり、物理アドレスなりを設定して下さい。
設定ファイルが完成したら、# sudo mv synergy.conf /etc/で/etc/配下へ移動。これで設定ファイルの設定は完了です。
次に起動スクリプトを作成します。
アプリケーション→AppleScript→スクリプトエディタ
を起動
do shell script “/usr/local/bin/synergys -f –config /etc/synergy.conf -n hogemac.local”と書き、任意の名前で保存。このスクリプトをMacログイン時に読ませれば、自動でsynergyを起動してくれます。

これでMacの設定はおしまい。

Win側

ダウンロードしたソフトを解凍、そして起動。
Use another compute’s shared keybord and mouseにチェックを入れ、Other Computer’s Host NameにMacのローカルIPを入れて下のTestを押下。
うまく行ったらStarを押下。これで起動します。

以上で設定終了です。

これで、Macとwindowsでマウスの共有ができます。

surround.vim のつかいかた

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

surround.vim のつかいかたです。Vim ライトユーザにもおすすめしたいので、にほんごで簡単につかいかたを書いてみます。

 導入

例により、vim.org から ダウンドードしてきます。

つい最近、バージョン 1.9 がでています。vim.org では Vim のバージョンが 6.0 対象と書かれていますが、ヘルプをみると Vim 7 以降がオススメとあるので、Vim 7以上でも安心です。

適当に展開して plugin, doc にファイルを置き、:helptags path/to/help(ヘルプの置いてあるパス) などとしてヘルプを引けるようにしておきます。

 テキストオブジェクトおさらい

surround.vim は vim のテキストオブジェクトに対する操作を拡張するプラグインです。テキストオブジェクトをざっくりおさらいです。

テキストオブジェクトはとてもおおざっぱに言えば、あるまとまりのテキストをのことです。このまとまりに対し操作を行なうことができます。

とりあえず見れば、ああ、あれか、と思うようなものなので、とりあえず見てみます。

# before

moji1    retu

というテキストがある場面で、moji1 のどこかの文字の上にノーマルモードでカーソルがあたっているとします。

そのときに diw とコマンド入力すると、”moji1″ というひとまとまりのテキストが削除されます(ここでは、単語という単位のひとまとまりのテキスト)。

# after

    retu

また、同じ場面で daw とすると “moji1 ” というひとまとまりのテキスト+次の単語までの空白文字が削除されます(ここでは単語と、それに付随している空白というまとまり)。

# after

retu

上で出てきた diw, daw のうちの、iw や、aw がテキストオブジェクトにあたります。diw の d は削除コマンド d のことで、テキストオブジェクト iw に対する操作コマンドになります。削除してインサートモードになる c や、ビジュアルモードの v などのコマンドが使えます。

テキストオブジェクトは iw, aw のほかにも以下のようなものがあります(見た目で、うごきがわかりやすいのをあげてみます。詳しくは :help text-objects)。

iw (単語)
aw (単語+次の文字までの空白)

it (タグの内部 <strong>hoge</strong> の hoge)
at (直近の外側にあるタグ含む文字列 <p><strong>hoge</strong></p> の場合、
    hoge や、strong 上でコマンドを入力すれば、<strong>hoge</strong> が対象になる)

i( (括弧内を以下略。 ib でも可。)
a( (括弧含め以下略。 ab でも可。 b は多分ブラケットの b です)

:h text-objects (ヘルプを見ましょう)

ということで、テキストオブジェクトはこんな感じのものでした。

 surround.vim について

surround.vim の基本的な概念

surround.vim での編集方法はざっくりで二種類の分けかたができます。

  1. 既に囲んでいるものに対する編集
  2. 新たにテキストオブジェクトを囲む編集
1. 既に囲んでいるものに対する編集

何かの記号やタグなどの、囲んでいるものに対して行なう編集です。そのまんまです。

ex:
'hoge'            (囲んでいるもの: ' )
"hoge"            (囲んでいるもの: " )
(hoge)            (囲んでいるもの: ())
<tag>hoge</tag>   (囲んでいるもの: <tag> )

[hoge]

{hoge}

といった文字列に対して利用できます。この「囲んでいるもの」たちを、削除したり、違うものに変えたりする、という操作ができます。

以下のコマンドの組みあわせで実現できます。

編集コマンド(d, c) + s(surround の s) + 囲んでるもの(‘ とか ” とか <tag> とか)

囲んでいるものを削除する場合は以下のようになります。

d + s + 囲んでるもの
ds'     (今カーソルのある文字列を囲んでいる ' を消す)
ds"     (" を)
ds(     (() を)
dst     (<tag> を)

囲んでいるものを変える場合は以下のようになります。

c + s + 囲んでるもの, + 変更したいもの
cs'"            (今囲んでいる ' を " に変える)
cs"<tag>        (今囲んでいる " を <tag> に変える)
cst<tag>        (直近で囲まれてるタグを <tag> に)
2. 新たにテキストオブジェクトを囲む編集

word に対する iw とかのテキストオブジェクトを、新たに囲む編集です。そのまんまです。

hoge
{ :foo => 'bar' }
he said, i am a pen.
hogehogehogehoge
yahhooooo konnichiha-

といった文字列を囲むという操作です。

以下のコマンドの組みあわせで実現できます。

ys + 範囲 + 囲む文字列

※ 以下では ‘*’ で、今カーソルのある位置を示し、||で囲まれた範囲はビジュアルモードで選択されているものとします。

befor                   →  after                     # コマンド

h*oge                   →  "hoge"                    # ysiw"
"↑ カーソル位置の単語を " で囲う

{ *:foo => 'bar' }      →  ( { :foo => 'bar' } )     # ysa{( or ysaBb
"↑ カーソル位置の {} で囲まれた文字列を () で囲う

he said, *i am a pen.   →  he said, "i am a pen."    # ys$"
"↑ カーソル位置がら行末までを " で囲う

hogehoge|hoge|hoge      →  hogehoge"hoge"hoge        # s"
"↑ 選択範囲を " で囲う

yahho*oooo konnichiha-  →  'yahhooooo konnichiha-'   # yss'
"↑ 行全体を ' で囲う。行指向操作。

以上が、surround.vim の機能、「既に囲んでいるものに対する編集」と「新たにテキストオブジェクトを囲む編集」です。

ほか

インサートモードでも使う

<C-G>s でインサートモードでも使えます。インサートモードで、以下のようなコマンドで操作します。

入力:
  <C-G>s<tag>
結果:
  <tag>*</tag> (* はカーソル位置。タグに囲まれた状態になる。)

入力:
  <C-G>s"
結果:
  "*"
自動でインデント(yS, S)

ys を yS に変えると囲うと同時にインデントもしてくれます。<div> などのタグで囲ったとき使うと少し便利かもしれません。

hello

という文字列のある行に対して、ySS<div> という操作をすると以下のようになります。

<div>
  hello
</div>
カスタマイズ

囲う文字列は自分で追加できます。vimrc とかに書いとくと使えるようになります。

let g:surround_コマンド = "開始 \r 終了"

let g:surround_45 = "<% \r %>"
"↑ 45 は - をコマンドに使うという意味。

let g:surround_61 = "<%= \r %>"
"↑ 61 は = のこと。この数値は、- とか = の上で :ascii すればわかります。

" 以下のように書くこともできます(直感的な書きかた)
let g:surround_{char2nr("-")} = "<% \r %>
let g:surround_{char2nr("=")} = "<%= \r %>

こう書いておくと、新たな – とか = の囲うコマンドが使えるようになります。

befor       →  after               # コマンド

'hoge'      →  <%= hoge %>         # cs'=
" ↑' で囲まれている文字列を <%=  %> で囲う

@hoge.name  →  <%= @hoge.name %>   # yss=
"↑ カーソルのある行を <%=  %> で囲う

ちなみに、上記の – とか = は、rails.vim + surround.vim という環境では、erb ファイルの編集時に使えるよう、はじめから定義されています。

もっとカスタマイズ

プロンプトでいろいろ尋ねながら入力した文字列を、囲うものの要素としてつかうことができます。

	let g:surround_{char2nr("d")} = "<div\1id: \r..*\r id=\"&\"\1>\r</div>"
	" hoge に ysiwd とかすると
	" id: というのが下のコマンドラインにでてくるので何かいれると
	" (たとえば main とか)
	" <div id="main">hoge</div>
	" というふうになります。
	
	let g:surround_{char2nr("a")} = "<a href=\"\r\">\1link_text: \r..*\r&\1</a>"
	" http://doruby.com という文字列に yssa などとすると、
	" link_text: などというプロンプトがでてくるので、何かいれると
	" (たとえば ナイスブログ とか)
	" <a href="http://doruby.com">ナイスブログ</a>
	" というふうになります。
	

プロンプトで尋ねるのは7つまで使えます。

もとからある t をツブすとタグを入力するとき残念なことになるので空いてる文字をさがして適当にわりあてましょう。

大変長くなってきました。長いのでもう終わりです。

快適 vim ライフをすごしましょう!

FireFox3.6のFileAPIを使ってドラッグ&ドロップでファイルアップロード

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


最近リリースされたFireFox 3.6ではFileAPIという機能が使えるようになりました。

今回はFileAPIで実現出来るようになったドラッグ&ドロップでのファイル選択を使ってファイルアップロード機能を実装してみます。

今回のスクリプトはこちらのサイトを参考に書きました。

Using files from web applications – MDC

ソースは以下のとおり。

<script language="javascript">
  function MultipartData(boundary) {
    this.boundary = boundary;
    this.data = "";
    this.data += "--" + boundary + "\r\n";

    this.append = function(name,value){
      this.data += "Content-Disposition: form-data; name=\"" + name + "\"\r\n\r\n";
      this.data += value + "\r\n";
    };

    this.appendFile = function(filename,filedata){
      this.data += "Content-Disposition: form-data; name=\"uploadfile\"; filename=\"" + filename +"\";\r\n";
      this.data += "Content-Type: application/octet-stream;\r\n\r\n";
      this.data += filedata + "\r\n";
    }

    this.appendBoundary = function(last){
      if(last != undefined && last == true){
        this.data += "--" + this.boundary + "--\r\n";
      } else {
        this.data += "--" + this.boundary + "\r\n";
      }
    }
  }

  function dragenter(e){
    e.stopPropagation();
    e.preventDefault();
  }

  function dragover(e){
    e.stopPropagation();
    e.preventDefault();
  }

  function drop(e){
    e.stopPropagation();
    e.preventDefault();

    var dt = e.dataTransfer;
    var files = dt.files;
    this.files = files;
    var xhr = new XMLHttpRequest();
    boundary = "---------------------------14737809831466499882746641449";
    var multipartData = new MultipartData(boundary);

    multipartData.append("param","value");
    multipartData.appendBoundary();
    multipartData.append("multibyte_param",encodeURI("日本語"));
    multipartData.appendBoundary();
    multipartData.appendFile(encodeURI(files[0].name),files[0].getAsBinary());
    multipartData.appendBoundary(true);

    xhr.open("POST","/files/upload",true);
    xhr.onreadystatechange = function (aEvt) {
      if (xhr.readyState == 4) {
        if(xhr.status == 200){
          alert("アップロードが完了しました。");
        } else {
          alert("アップロードに失敗しました。");
        }
      }
    };
    xhr.setRequestHeader("Content-type","multipart/form-data; boundary="+boundary+ ";");
    xhr.setRequestHeader("Content-length",multipartData.data.length);
    xhr.sendAsBinary(multipartData.data);
  }

  document.getElementById("drop_area").addEventListener("dragenter",dragenter, false);
  document.getElementById("drop_area").addEventListener("dragover",dragover, false);
  document.getElementById("drop_area").addEventListener("drop",drop, false);
</script>

ドラッグ&ドロップでのファイル選択は以下のようにドロップイベントでイベントオブジェクトのdataTransferを取得するだけで簡単に実装できました。

    document.getElementById("drop_area").addEventListener("drop",drop, false);
  function drop(e){
    e.stopPropagation();
    e.preventDefault();

    var dt = e.dataTransfer;
    var files = dt.files;
    this.files = files;

さて、あとは取得したファイルのデータをアップロードするだけなのですが、これがなかなか面倒です。ファイルと一緒に他のパラメタも送信したいので、データやバウンダリーなどを追加していけるオブジェクトを作ってmultipartなリクエストを自前で作ってポストしています。

一つ注意点としては、日本語をそのままパラメタに入れてsendAsBinaryで送信するとエラーが起きるので、適当にencodeURIでエンコードして送信しています。

というわけでファイルアップロード機能が実装できました。

受け手のサーバ側はRailsで作りましたが、PHPなどでもそのまま受け取れると思います。FileAPI便利ですね!

cameraOverlayView に触れる

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちわ。iPhoneをもたないでiPhone開発するendです。
今日もちょっとiPhoneで遊んでみました。

UIImagePickerControllerが iPhone0S3.1 機能拡張された cameraOverlayView プロパティを利用して遊んでみました。
このcameraOverlayViewプロパティは、カメラを起動し表示される画面の上に、簡単に情報などを重ねて表示することができるものです。
拡張現実(AR)を作成するのに便利なものというところでしょうか。

今日は、ARというカッコよいものは作成できませんが、このcameraOverlayView に触れてみたいと思います。

 
    // UIImagePickerControllerのインスタンス化
    pickerController = [[UIImagePickerController alloc] init];
    // カメラモードの指定
    pickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    // シャッターボタンなどを隠す
    pickerController.showsCameraControls = NO;
    // overlayViewは, UIViewに野菜の画像を複数 addSubView したもの。
    // それをcameraOverlayViewにセットする。
    pickerController.cameraOverlayView = overlayView;
    // UIImagePickerの表示
    [self presentModalViewController:pickerController animated:NO];

 あとは、NSTimerをつかって段々近づいてくる感じにゴニョゴニョにしてみました。

1
2
3

お。簡単!!

なぜ野菜なのかは特に理由はありません。

まだ発売されていないiPadを表示して、早くもiPadをゲットしたぜ!! なんて冗談をやろうとしたのだが、

やってみたら、あまり格好よくなかったので、野菜にしました。

PowerPointでのプレゼンテーションでのひと工夫

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

みなさま、こんにちはえーきち。。です。

みなさまPowerPointの”アニメーション”機能って使っていますか?

取り入れますと文字の羅列だった資料にメリハリが生まれ、

相手に伝えたいことを強調することができます。

ではそのいろいろ紹介していきましょう。。。

アニメーションには大きく2種類存在してます、

 (1)ページとページをまたぐ際のアニメーション

 (2)ページ内でのアニメーション

まずは(1)ページとページをまたぐ際のアニメーション、をいろいろ見ていきましょう。

ファイルをひらいてメニュータブの[アニメーション]を選択ください。

囲った部分が主にページとページをまたぐ際のアニメーションの設定になります。

 (A)アニメーションの種類

 (B)アニメーション切り替え時の音/速度

 (C)アニメーション切り替えのタイミング

になります。

(B),(C)に関しては名前のままの設定ですので細かくは省きますが

実際に使用した感じですと”画面切り替え時の速度”は”普通”もしくは”遅く”がいいかもしれません。

(A)のアニメーションの種類については、表示されている13種類(デフォルト+12種類)以外に

実は多数存在しているのはお気づきでしょうか?

(かくいう自分はけっこう最近まで気づいてませんでした)

(A)の右端にスクロールボタンが存在しています、これでさらに種類を選択することができ合計59種類(57種類+デフォルト+ランダム)バリエーションがあります。

この中から独断と偏見による使い勝手のよいものを3つをご紹介します。

 「時計回りの輪,4 スポーク」

 「スライドアウト右下へ」

 「サークル」

いずれも、ユーザーの視点があまりぶれずにアニメーションが行われているものです。

内容やページ内のアニメーションの有無にあわせて選択すべきですが

ひとつ選択したら同じアニメーションで通すほうが

ユーザーにとっては見やすいかもしれません。

いずれにしても何度も確認を行い作成してください。

次回は(2)ページ内でのアニメーションを説明させていただきます。

【Ruby】半角/全角バリデータ【Rails】

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

入力項目には必ずといってもいいほど必要な「バリデーション」。
今回は、「その文字列が半角 or 全角文字のみであるか」というバリデーションをRubyで紹介します。今回の条件は以下の通りです。

半角文字:半角カタカナ、半角英数字、一部の記号(濁点、半濁点など)
全角文字:半角文字でないもの
メールアドレス、URLは除外

では、半角/全角のバリデーションのソース公開☆

KCODE = 'u'

  def self.hankaku?(str)
    return nil if str.nil?
    # 半角のみOKなので、全角が混ざっているとfalseが返る
    unless str.to_s =~ /^[ -~。-゚]*$/
      return false
    end
  return true
  end

  def self.zenkaku?(str)
    return nil if str.nil?
    # 全角のみOKなので、半角が混ざっているとfalseが返る
    unless str.to_s =~/^[^ -~。-゚]*$/
      return false
    end
    return true
  end

このままコピペすれば使えるはずです。
内容は非常に簡単で、正規表現で半角文字列にマッチするか否かを調べているだけです。

今回半角用と全角用を用意したのは、「その文字列が半角 or 全角文字だけで構成されているかどうか」を調べたかったからです。
hankakuメソッドだけでは、文字列が「全角文字と半角文字の混合」なのか「全角文字だけなのか」がわかりません。

では、住所のふりがなの入力チェックなんかに、ぜひ利用してみてください。

ブロックを使ったリファクタリング

0

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

こんにちは、shimadaです。

今日は、Rubyの特徴の一つであるブロックを引数にとるメソッドの書き方を説明します。

題材として、「あちこちに同じ順番で処理が並んでいる時のコードの整理」を取り上げたいと思います。

重要な処理を行う場合に、開始、終了、エラーが起こったときの詳細を記録しなければならない。

そんな時の処理は、だいたいこんな風になるのではないでしょうか。

class SimpleWorker
  def action(arg)
    logger = Application.logger
    begin
      logger.info("SimpleWorker.action", "start")
      do_something
    rescue => e
      logger.error("SimpleWorker.action", e.message + e.backtrace.join("¥n"))
      logger.info("SimpleWorker.action", "abnormal end")
      raise
    else
      logger.info("SimpleWorker.action", "normal end")
    end
  end
end

class ComplexWorker
  def complex_action(arg1, arg2, arg3)
    logger = Application.logger
    begin
      logger.info("ComplexWorker.complex_action", "start")
      do_complex_thing1
      do_complex_thing2
      do_complex_thing3
      do_complex_thing4
      do_complex_thing5
    rescue => e
      logger.error("ComplexWorker.complex_action", e.message + e.backtrace.join("¥n"))
      logger.info("ComplexWorker.complex_action", "abnormal end")
      raise
    else
      logger.info("ComplexWorker.complex_action", "normal end")
    end
  end
end

同じような記述がたくさん並んでいてうんざりですね。

気になる点は2つあります。

  1. “SimpleWorker.action” という文字列が何度も出てきてうざい。
  2. begin .. rescue .. else .. end のパターンが似ているっていうか同じ

これをリファクタリングしてみましょう。合い言葉は「DRY」です。

1.は変数に入れてしまえばなんとかなりそうです。

2.については、「同じ処理の並びを共有する」というところからGoFで有名なテンプレートメソッドパターンとかを思いつきます。

でも、shimadaは継承が割と嫌いです。

「重要な処理」というのはシステムの色々なところにちょこちょこ出てくるので、「必ず同じ親クラスから派生していなければいけない」という制限をかけるのは、システムを設計する上で大きな足かせになりえるのです。

ではどうするか。Rubyの特色はmix-inです。そしてshimadaの好物は高階関数です。

手続きを引数にとる手続き、つまりブロックを受け取るメソッドをモジュールとして書いてみましょう。

module LogHandler
  def logging(task_name)
    logger = Application.logger
    begin
      logger.info(task_name, "start")
      yield(logger)
    rescue => e
      logger.error(task_name, e.message, e.backtrace.join("¥n"))
      logger.info(task_name, "abnormal end")
      raise
    else
      logger.info(task_name, "normal end")
    else
    end
  end
end

早速、各クラスにincludeして使ってみます。

class SimpleWorker
  include LogHandler
  def action(arg)
    logging("SimpleWorker.action") do |logger|
      do_something
    end
  end
end

class ComplexWorker
  include LogHandler
  def complex_action(arg1, arg2, arg3)
    logging("ComplexWorker.complex_action") do |logger|
      do_complex_thing1
      do_complex_thing2
      do_complex_thing3
      do_complex_thing4
      do_complex_thing5
    end
  end
end

すっきりしましたね。

また、ブロックを使ったプログラミングで混乱しがちなのが、「ブロックが受け取る引数」です。

この例ではyield(logger)に渡された値を do |logger| で受け取っています。

処理の流れが普通の関数呼び出しの逆になるので、初めての人は慣れるまでたくさん素振りして下さい。

ところでブロックを引数にとるメソッドの書き方には何種類かありますが、引数名に ‘&’ をつける方法は、受け取ったブロックを他のメソッドにそのまま渡したい時に便利です。

def method1(obj, &block)
  obj.method2(&block)
end

例を書くための例といった感じで、なにをしたいのかさっぱり不明ですが、実際のプロジェクトでまさにこういうコードが実際に必要になったというか先週書いたばかりです。プログラミングって不思議ですね!

ところで、振る舞いのリファクタリング以外でも、ブロック渡しは例えばテストを書くとき等にも役に立ちます。

shimadaがどんな風に使ったかは、また今度の機会に紹介したいと思います。

みなさんもブロックをどんどん使ってRubyを楽しんで下さい。

最近人気な記事