波打際のブログさん

主に、プログラミング備忘録など。

RubyでInteger値を持つEnum、Inumを作ってみた。

この記事はバージョン2.x系の記事で、現行バージョンの3.xとは互換性がありません。

RailsのEnumと相性が悪い人向けのInumをバージョンアップさせた話。 - 波打際のブログさん
こちらの記事がバージョン3.x系の記事となります。

はじめに

 最近Rubyを使用する機会が増えてきたのですが、Rubyに列挙型の実装がありませんでした。そこで、Gemを探してみたものの、C言語ライクなInteger値を持つEnumで、思ったとおりの事が可能なGemが存在しなかったので、自前で実装する機会がありました。その実装の仕組みを参考に汎用的に使用可能なEnum(Inum)を作成してGemとして公開しました。


  • 2013年11月28日、機能追加に伴い追記しました。
  • 2013年12月03日、バージョン2.0.0リリースに伴い記事を修正しました。
  • 2013年12月10日、バージョン2.0.5リリースに伴い記事を修正しました。

Inumでやりたいこと(実装したこと)

  • CやC#ライクなInteger値を保持しているEnumで有ること。
  • 列挙型のメンバーを表すインスタンスは、一つしか存在しないこと。(メンバーがシングルトン)
  • 値とメンバー名の相互変換ができること。
  • i18nを使用して列挙型からローカライズした文字列を取得できること。

使い方

Gemのインストール

 いつもどおりです、RailsならGemfileなどにinumの依存関係を追加します。

gem 'inum'

列挙型の定義

 Inum::Baseを継承したクラス内で、define_enumメソッドを呼び出して列挙型を定義します。define_enumの第一引数には列挙型のラベル(名前)をSymbolで、第二引数には列挙型の値をIntegerでそれぞれ渡します。第二引数の列挙型の値は省略可能で、省略された場合0から自動的にインクリメントされます。

class AnimeType < Inum::Base
  define_enum :NyarukoSan,   0
  define_enum :KiniroMosaic, 1
  define_enum :KillMeBaby,   2
end

列挙型の使用方法

Inumを継承した列挙型は次のように使用出来ます。

p AnimeType::NyarukoSan       # => NyarukoSan 
p AnimeType::NyarukoSan.to_i  # => 0

# 数値や、シンボルから列挙型へのパーズが可能です。
type = AnimeType::parse(1)
p type.equal?(AnimeType::KiniroMosaic)  # => true

# eql?メソッドを使用すると、比較対象のオブジェクトをパーズ後比較します。
p AnimeType::KillMeBaby.eql?('KillMeBaby')  # => true

# underscoreメソッドを使用すると、Enumのラベルをアンダースコアつなぎの文字列にします。
p AnimeType::NyarukoSan.underscore  # => nyaruko_san

# +と-の単項演算子も定義されています。
p AnimeType::NyarukoSan + 1  # => KiniroMosaic
p AnimeType::KillMeBaby - 1  # => KiniroMosaic

# eachによる繰り返しもできます。
AnimeType::each do |enum|
  p enum
end

Enumerableモジュールをextendし、Comparableモジュールをincludeしていますので、mapなどのメソッドが利用可能です。

その他のメソッドについてはドキュメント(Class: Inum::Base — Documentation for alfa-jpn/inum (master))を参照してください。

その他の使い方

i18nを利用する

 Enum(Inum)で to_t メソッドを利用することで、i18nローカライズされた文字列を取得することが可能です。

次のような多言語化ファイルが存在した場合

ja:
  inum:
    anime_type:
      nyaruko_san:   "這いよれ!ニャル子さん"
      kiniro_mosaic: "きんいろモザイク"
      kill_me_baby:  "キルミーベイベー"

translateメソッドを用いると次の結果になります。

p AnimeType::NyarukoSan.translate    # => 這いよれ!ニャル子さん
p AnimeType::KiniroMosaic.translate  # => きんいろモザイク
p AnimeType::KillMeBaby.translate    # => キルミーベイベー

i18nの辞書定義はデフォルトでは inum.{名前空間(モジュール名)}.{クラス名}.{メンバー名} を参照しますが、i18n_keyクラスメソッドをオーバーライドすることで参照先を書き換えることも可能です。

class AnimeType < Inum::Base
  define_enum :NyarukoSan,   0
  define_enum :KiniroMosaic, 1
  define_enum :KillMeBaby,   2

  # i18n_keyをオーバーライド(内容はデフォルトの定義です。)
  def self.i18n_key(label)
    # underscoreメソッドでキャメルをアンダースコアつなぎにした上で、::を.に置き換えています。
    Inum::Utils::underscore("inum::#{self.name}::#{label}")
  end
end
Enumをクラスの変数に結びつける

 DefineCheckMethodモジュールを利用すると、クラスの変数とEnumを関連付けし、判別用メソッドを自動的に定義することが可能です。

class Anime
  extend Inum::DefineCheckMethod

  attr_accessor :type

  define_check_method :type, AnimeType
end

define_check_methodにカラム名と関連付けさせるInumを継承したEnumクラスを渡して判別用メソッドを自動的に定義しています。

anime = Anime.new
anime.type = AnimeType::NyarukoSan

p anime.nyaruko_san?    # => true
p anime.kiniro_mosaic?  # => false


# typeにはパーズ可能なメソッドなら何でも入ります。
anime.type = 1
p anime.kiniro_mosaic? # => true

また、define_check_methodの第三引数は省略可能オプションになっていて、prefixの設定が可能です。prefixを指定すると定義されるメソッドの先頭にprefixが付与されます。

class Anime
  extend Inum::DefineCheckMethod

  attr_accessor :type

  define_check_method :type, AnimeType, 'anime'
end

prefixにanimeを指定すると、定義されるメソッドは次のようになります。

anime = Anime.new
anime.type = AnimeType::NyarukoSan

p anime.anime_nyaruko_san?    # => true

おわりに

 何か改善案あればpullrequest、issue気軽に投げてください、githubアカウントお持ちでない方はこのブログのコメント欄にもお気軽にどうぞ。

関連リンク

rbenv-railsを使用して、バージョン指定したrailsプロジェクトを30秒で生成する。

はじめに

 以前、Bundlerを使ってローカルなRailsから新規プロジェクトを作る 方法を紹介しましたが、プロジェクトを生成するたびに実行するのは結構面倒です。そこでこの作業を自動的に行うシェルスクリプトを書いたので紹介します。 → 追記:せっかくなので rbenv のプラグインにしました。


想定環境

  • rbenvがインストールと設定が完了している事。 → こちらを参考に設定&インストール。
  • gemのbundler(bundleコマンド)がインストールされている事。 → こちらのBundlerの項目を参照(コマンド3行です。)

インストール

rbenvのpluginsフォルダにrbenv-railsをgit cloneします。
# rbenvのプラグインフォルダにクローンする。
$ git clone https://github.com/alfa-jpn/rbenv-rails.git ~/.rbenv/plugins/rbenv-rails

インストールは以上です。

使い方

引数

rbenv rails <プロジェクト名> <railsのバージョン>

実行例

# rails 4.0.0 を使用して example_project を作成する。
rbenv rails example_project 4.0.0

もし、途中でsqlite3関連のエラーが出た際は libsqlite3-dev をパッケージマネージャからインストールしてください。

補足

 rbenv-railsは引数で指定されたバージョンのrailsを、ローカル環境(カレントディレクトリ以下のvendorフォルダ)にインストールします。そのrailsを利用して新規にrailsプロジェクトを生成し、生成されたrailsプロジェクトの中で bundle install コマンドを実行し、プロジェクトディレクトリの vendor/bundle に gemをインストールします。

 そのため、rbenv-railsを使用して生成されたrailsプロジェクトは、グローバル環境に干渉しないrailsプロジェクトになります。

補足2

 おそらく、rbenv rails コマンド実行後パッケージのダウンロードに時間がかかると思いますが、コマンドを入力する時間は30秒以内だったはずです。誇大タイトルでしたゆるしてくだ・・・おや誰か来たようだ、うわなにをするやめくぁwせdrftgyふじこlp;

Playframework2.1のCSRF対策

はじめに

 Playframework2.1では新機能としてCSRF対策が追加されたそうです。そもそもCSRF対策の機能はPlayframework1.x系には存在した機能ですが、2.0では機能そのものが消えていました。当然といえば当然の復活です。

 公式の新機能紹介( Highlights )にも New Filter API and CSRF protection と大きく取り上げています。

新機能紹介のページを読み進めていくと、

The filters project that is part of the standard Play distribution contain a set of standard filter, such as the CSRF providing automatic token management against the CSRF security issue.

http://www.playframework.com/documentation/2.1.x/Highlights

 素晴らしい!これでCSRF問題の苦悩から解放される・・・!と、感動すると同時にCSRFに関する説明はここで終わっていることに気づきます。

そうです、肝心なhow toや関連するリンクが一切ないのです!!

と、言うことで必死にぐぐって使えるようになったので結果をまとめておきます。

編集するファイル

project/build.scala

 filtersを依存関係に追加します。appDependenciesの部分を下記のように編集します。

val appDependencies = Seq(
	// Add your project dependencies here,
	javaCore,
	javaJdbc,
	javaEbean,
	filters //←この行を追加
)

app/Global.java

 アプリケーション全体の設定などを記述するGlobalクラスに、次のメソッドを追加します。Globalクラスは初期状態では存在しないので生成して、app直下(eclipse等では、default packages)に配置します。 Global.javaに関する詳しい記述は JavaGlobal を参照してください。

import play.*;
import play.api.mvc.EssentialFilter;
import play.filters.csrf.CSRFFilter;

public class Global extends GlobalSettings {

	@Override
	@SuppressWarnings({"rawtypes", "unchecked"})
	public <T extends EssentialFilter> Class<T>[] filters() {
		Class[] filters = {CSRFFilter.class};
		return filters;
	}

}

 filters()でCSRFFilterのクラスを返すことで、リクエストをインターセプトした際に、フィルターが呼び出されて処理が行われるみたいです。

使い方

 設定が終わったので、あとはフォームに設定を適用するだけで自動的にCSRF対策が行われます。具体的には helper関数を利用してフォームの宣言を行う際のactionに helper.CSRF関数を使用します。

@helper.form(action = helper.CSRF(routes.NyarukoController.postSanti)) {

}

こんなかんじです。


Bundlerを使ってローカルなRailsから新規プロジェクトを作る

はじめに

 本項では、ローカルなRailsから新規プロジェクトを作成し、gitで管理を行います。グローバルな環境にRailsをインストールしないことで、複数のバージョンのRailsを使い分けしやすくなります。

環境

Bundlerのインストール

 Bundlerとは、RubyGemsのラッパーで、Gemのバージョン管理ツールです。Bundlerを使うことで複数の環境下で依存関係の解決が容易にできます。

# rbenvを使用してBundlerをインストール。
$ rbenv exec gem install bundler
 
# rehashする。
$ rbenv rehash
 
# インストールの確認。
$ rbenv exec bundle --version
Bundler version 1.3.5

Bundlerを使ってローカルなRailsからプロジェクトを作る。

これ以降の手順を自動化するシェルスクリプトを書きました。詳細は バージョンを指定したRailsプロジェクトを自動で生成する。 - 波打際のブログさん を参照してください。


 Bundlerを使ってローカルにRailsをインストールし、ローカルなrailsからプロジェクトを生成します。

プロジェクトを生成したいディレクトリにGemfileを生成する。

$ bundle init

Gemfileの編集

 initすると、カレントディレクトリにGemfileが生成されるので、それを編集してGemfileの依存関係にRailsを追加します。

$ echo 'gem "rails", "4.0.0"' >> Gemfile

Bundlerを利用してローカルにRailsをインストール

 bundle installコマンドで、Gemfileの依存関係が解決されるのでRailsがインストールされます。

$ bundle install --path vendor/bundle

Railsプロジェクトを生成する。

 インストールされたRailsを利用して、Railsのプロジェクトを生成します。下記コマンドを実行するとカレントディレクトリの下に Nyaruko プロジェクトが生成されます。

$ bundle exec rails new Nyaruko --skip-bundle

 この時に --skip-bundle を指定しないとグローバルな環境が汚染されます。

不要なファイルの削除

 ローカルなRailsはもう使用しないので削除します。

rm -f Gemfile*
rm -rf .bundle
rm -rf vendor

 以上でローカルなRailsから新規Railsプロジェクトの生成が完了しました。

Railsプロジェクトをgitで管理できるようにする。

 生成したRailsプロジェクトをgitで管理できるようにします。

gitリポジトリ化する

 まずは、プロジェクトをgitリポジトリ化します。

Nyaruko $ git init
Nyaruko $ git add .
Nyaruko $ git commit

依存関係の解決

 生成されたRailsプロジェクトを使用可能にするために、bundle installを実行して、依存関係を解決します。

Nyaruko $ bundle install --path vendor/bundle

.gitignoreの設定

 依存関係の解決のために生成されたファイル類は、gitにpushすべきものではないので、無視するように.gitignoreを修正します。

Nyaruko $ echo '/vendor/bundle' >> .gitignore

 後はaddしてcommitすれば、gitで管理されたRailsプロジェクトの生成が完了です。

Railsの起動

 同梱されているWEBRickRailsを起動します。

Nyaruko $ bundle exec rails server

起動できなかったときは、Gemfile中の

gem 'therubyracer', :platforms => :ruby

をコメントアウトしてもう一回 bundle installを実行すると動くかも。。

rbenvを使って3分でRubyをインストールする方法まとめ。

はじめに

 最近モダンなrbenvを使って、3分の作業時間でRubyをインストールできたのでその方法をまとめておきます。

rbenv

 rbenvはrvmと同様にRubyのバージョン管理ソフトです。rvmよりも黒魔術を使わないため、副作用が少なく、最近モダンらしいです。乗るしかないこのビッグウェーブに。

環境

  • CentOS6.4
  • SSHでgit(git-hub)が利用できる環境(本項では解説しません。)

rbenv

rbenvのインストール

 インストールは非常に簡単で、公式リポジトリ(GitHub - rbenv/rbenv: Groom your app’s Ruby environment)をgitクローンしてbashにパスを通して、bash起動時に初期化されるように設定するだけで完了です。

# gitでローカルの ~/.rbenv にクローンを作る。
$ git clone git@github.com:rbenv/rbenv.git ~/.rbenv

# bashにパスを通す。(ubuntuの場合は .bash_profile ではなく .profile)
$ echo 'export RBENV_ROOT="$HOME/.rbenv"' >> ~/.bash_profile
$ echo 'export PATH="$RBENV_ROOT/bin:$PATH"' >> ~/.bash_profile

# bash起動時にrbenvの初期化を行うように設定。
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile

# シェルの再起動。
$ exec $SHELL -l

# インストールできているか確認。
$ rbenv --version
rbenv x.x.x-xx-xxxxxx # バージョンは環境によって異なります。

ruby-buildのインストール

 ruby-buildのインストールは、rbenvよりも更に簡単で、先ほどのrbenvのプラグインフォルダに、公式リポジトリ(GitHub - rbenv/ruby-build: Compile and install Ruby)をgitクローンするだけで終わりです。

# gitでローカルにクローンする。
$ git clone git@github.com:rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

 以上でRubyをインストールするためのrbenv環境が整いました。

Rubyのインストール

 rbenvを利用してRubyをインストールします。インストール可能なパッケージを install -l オプションで調べてインストールを行います。

# インストール可能なパッケージ一覧を調べる。
$ rbenv install -l
 
# 実行時に一番新しいRubyをインストールしてみた。
$ rbenv install 2.0.0-p247

# デフォルトのRubyに設定する。
$ rbenv global 2.0.0-p247

 rbenvがソースコードコンパイルを行うため、Rubyのインストールには時間がかかりますが、作業時間では無いのでノーカンで・・・

Apacheのログ設定まとめ

はじめに

 apacheのログについて、調べる機会があったので備忘録としてまとめておきます。

環境

  • CentOS6.4
  • apache2.4.4

バーチャルホスト毎にログを記録する

 バーチャルホストで複数のサービスを運営している際に、それぞれのホスト毎にログファイルを分けると管理が容易になります。

バーチャルホスト設定の編集

 設定はとても簡単で、バーチャルホストの設定(conf/extra/httpd-vhosts.conf)にCustomLog(アクセスログ)とErrorLog(エラーログ)の設定を記述するだけです。

<VirtualHost *:80>
    DocumentRoot "/htdocs/nyaruko"
    ServerName www.nyaruko.cqc
    CustomLog logs/nyaruko_server_access_log common
    ErrorLog  logs/nyaruko_server_error_log
</VirtualHost>

ログのローテートを行う

 日別や週別、月別などにログを分けて保存することをログのローテートと言います。ログのローテート方法はいくつか有りますが、今回はapacheに付属しているrotatelogsを利用する方法と、logrotateを利用する方法の2つを紹介します。

rotatelogsを利用する

 apacheに付属しているログのローテートツールです。ログの出力先としてパイプすることでログを分けて管理することができます。

CustomLog "|/usr/local/apache2/bin/rotatelogs /usr/local/apache2/logs/nyaruko_access_log_%Y%m%d 86400 540" common
第一引数

 ログの出力先を記述します。今回は /usr/local/apache2/logs/nyaruko_access_log_%Y%m%d と記述しているので、logs/nyaruko_access_log_20130814 という形式で出力されます。

第二引数

 ログの周期を記述します。86400sec=24h

第三引数

 時差を記述します。540min = 9h GMT(日本標準時)

logrotateを利用する

 CentOS6.4に標準で搭載されているlogrotateを利用すると、apacheに限らず様々なログを容易にローテートできます。 /etc/logrotate.d ディレクトリ以下に、新たにファイルを作成して設定を記述することでローテートが可能です。今回は /etc/logrotate.d/httpd-nyaruko という名前でファイルを作成して設定を記述しました。

# 対象のログファイル
/usr/local/apache2/logs/nyaruko_access_log {
 
	# ローテートの実行方式
	# 移動&作成		: create
	# コピー			: copy
	# コピー&削除		: copytruncate
	create	
 
	# ログファイルが見つからない際の挙動
	# エラーを吐く		: nomissingok
	# エラーを吐かない		: missingok
	missingok
 
	# ログファイルが空の際の動作
	# 空でも実行		: ifempty
	# 空の場合は実行しない	: noifempty
	notifempty
 
	# ローテーションの頻度
	# 日単位			: daily
	# 週単位			: weekly
	# 月単位			: monthly
	daily
 
	# 生成されるログファイル数の上限
	rotate 7
 
	# 圧縮設定
	# 圧縮			: compres
	# 未圧縮			: nocompress
	# 遅延圧縮		: delaycompress
	compres
 
	# ローテート時にスクリプト実行
	sharedscripts
	postrotate
		/sbin/service httpd reload
	endscript
}

 apache+logrotateではローテート実行時に、sharedscripts設定を記述して再起動を行います。再起動しないでローテートする場合はcreateではなくcopytruncateを指定します。しかし、ローテート中にアクセスがあった場合のログが記録されないのでログに漏れが発生します。

 nginx+logrotateの場合は再起動せずに、ログの漏れもなくローテートが実行できますが、その設定はまた次の機会に・・・。

所感

 apacheでログをローテートするならrotatelogsを使ったほうが、サービスを止めたり、ログの紛失がなくローテートができそう・・・ただしrotatelogsはログの削除ができないので、自分でシェルスクリプトを書いてcronで叩かないといけない。。。さらに、ログ一回吐き出すたびにrotatelogsにパイプするのって負荷がかかりそうな・・・よし、俺のVPSにはnginx使おう←イマココ

PHP5.4でLibpuzzleを使って画像の類似度判定を行う

はじめに

 Libpuzzleとは特徴検出のアルゴリズムにより、高速な画像類似度判定を行うことができるライブラリです。しかし、このライブラリをPHP5.4で利用しようとした場合、エクステンションがPHP5.4からロードされた瞬間にセグメンテーション違反(segmentation fault)が発生し強制終了させられます。

 調査した結果、原因はPHP用のエクステンションのメモリ確保方法に問題があると判断しました。そこで、PHP5.4に対応したエクステンションの開発を行いました。今回はそのエクステンションの導入と簡単な動作確認用のPHPプログラムを作成します。

 また、この組み合わせで本家のPHPエクステンションがエラーを履かない場合は、本家を利用したほうが良いと思います。

環境

  • CentOS6.4
  • apache2.4.4
  • PHP5.4.16

Libpuzzleの導入

 PHP用のエクステンション導入の前にLibpuzzleを導入します。

GD2のインストール

 LibpuzzleはGD2を利用して類似度判定を行うので、yumを利用してインストールします。

$ sudo yum install gd gd-devel

Libpuzzleのインストール

 GD2のインストールが終わったら次はLibpuzzle本体をインストールします。本体は本家サイト(Libpuzzle - A library to find similar pictures)からダウンロードします。

$ wget http://download.pureftpd.org/pub/pure-ftpd/misc/libpuzzle/releases/libpuzzle-0.11.tar.bz2
$ tar -jxvf libpuzzle-0.11.tar.bz2

Libpuzzle-0.11 $ ./configure
Libpuzzle-0.11 $ make clean
Libpuzzle-0.11 $ make
Libpuzzle-0.11 $ sudo make install

PHP用エクステンションのインストール

 本来ならば、libpuzzle-0.11.tar.bz2に同梱されている php/Libpuzzle を利用しますが、冒頭で述べたようにPHP5.4でsegfaultを吐いてしまったので、PHP5.4用に開発を行ったphp-libpuzzle5(alfa-jpn/php-libpuzzle5 · GitHub)を利用します。

php-libpuzzle5のインストール

 git-hubのreleaseからバージョン1.0.0をダウンロードしてインストールします。

$ wget https://github.com/alfa-jpn/php-libpuzzle5/archive/1.0.0.tar.gz
$ tar -zxvf php-libpuzzle5-1.0.0.tar.gz

php-libpuzzle5-1.0.0 $ phpize
php-libpuzzle5-1.0.0 $ ./configure
php-libpuzzle5-1.0.0 $ make clean
php-libpuzzle5-1.0.0 $ make
php-libpuzzle5-1.0.0 $ sudo make install

php.iniの設定

 PHPの設定ファイル(/usr/local/lib/php.ini)にエクステンションを読み込む設定を追記します。

extension=libpuzzle5.so

以上でインストール作業は完了です。

テストプログラムを書いてみる

 こんな感じでプログラムを記述します。 $dは0〜1の間の値で、0に近いほど画像が似ていると判定されています。

<?php

$cvec1 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '0.jpg');
$cvec2 = puzzle_fill_cvec_from_file(dirname(__FILE__) . '1.jpg');
$d = puzzle_vector_normalized_distance($cvec1, $cvec2);
echo '類似度は' . $d . 'です。';

?>

 puzzle_fill_cvec_from_fileの引数を絶対パスで指定している点に注意して下さい。PHPから呼び出せる関数は本家のlibpuzzleエクステンションと同様ですので、本家のドキュメントを参照してください。