開発


6
Sep 09

named_scopeの引数による条件分岐

named_scopeに引数を渡した場合、その型によって設定するSQLを動的に変更したいことがある。 こういうときにはlambdaに渡しているのブロック中で条件分岐すれば良い。


named_scope :member, lambda { |m|
  case m
  when Member
    { :conditions => { :member_id => m.id } }
  when Integer
    { :conditions => { :member_id => m } }
  else
    {}
  end
}

要するに、lambdaに渡したブロックの処理の結果、findに渡すHashパラメータが返ればそれでok、という感じなんじゃないかと。


29
Jul 09

Ruby:Hashの値でソートしたArray

Ruby 1.8.7で。

hash.map { |k, v| { :name => k, :count => v } }.sort { |a, b| b[:count] <=> a[:count] }

28
Jul 09

SQL:group byとdistinctの同時使用でcount

MySQL 5.0.x

以下の例でcount(*)count(foo)と同じ。

全データ

fooとbarの2カラムがあって、それらの値はいくつか重複している。

mysql> select * from hoges;
+----+------+------+---------------------+---------------------+
| id | foo  | bar  | created_at          | updated_at          |
+----+------+------+---------------------+---------------------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 | 
+----+------+------+---------------------+---------------------+
|  3 |    1 |   11 | 2009-07-27 16:35:33 | 2009-07-27 16:35:33 | 
|  4 |    2 |   12 | 2009-07-27 16:35:33 | 2009-07-27 16:35:33 | 
+----+------+------+---------------------+---------------------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 | 
+----+------+------+---------------------+---------------------+
|  7 |    3 |   11 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 | 
|  8 |    4 |   12 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 | 
+----+------+------+---------------------+---------------------+

countのみ

当然ながら全レコード数8個が得られる。

mysql> select *, count(*) as count from hoges;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     8 | 
+----+------+------+---------------------+---------------------+-------+

distinct付きfooでcount

重複するfooを省いてカウントするから、foo=1-4で4個。

mysql> select *, count(distinct foo) as count from hoges;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     4 | 
+----+------+------+---------------------+---------------------+-------+

barでgroup byしてからdistinctなしでcount

bar=11,12のレコードはそれぞれ3個あるから3とカウントされる。bar=21,22はそれぞれ1個しかないから1とカウントされる。

mysql> select *, count(*) as count from hoges group by bar;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     3 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     3 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+

barでgroup byしたものをdistinct付きfooでcount

id=1と3、id=2と4はbarだけでなくfooも一致するからそれぞれ1つとカウントされて、id=7,8と合計して2となる。

mysql> select *, count(distinct foo) as count from hoges group by bar;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+

fooとbarの両方でgroup byしてcount

この結果はfooとbarの順番を入れ替えても同じ。

mysql> select *, count(*) as count from hoges group by foo,bar order by id;
+----+------+------+---------------------+---------------------+-------+
| id | foo  | bar  | created_at          | updated_at          | count |
+----+------+------+---------------------+---------------------+-------+
|  1 |    1 |   11 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
|  2 |    2 |   12 | 2009-07-27 16:35:23 | 2009-07-27 16:35:23 |     2 | 
+----+------+------+---------------------+---------------------+-------+
|  5 |    1 |   21 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
|  6 |    2 |   22 | 2009-07-27 16:36:03 | 2009-07-27 16:36:03 |     1 | 
+----+------+------+---------------------+---------------------+-------+
|  7 |    3 |   11 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 |     1 | 
|  8 |    4 |   12 | 2009-07-27 16:37:10 | 2009-07-27 16:37:10 |     1 | 
+----+------+------+---------------------+---------------------+-------+

21
Jul 09

Growlでautospecの結果を表示

Growlの準備

  • Growlをインストール
  • インストーラと一緒に入っているExtra/growlnotify/install.shを実行する
  • autotest + growl で楽々テストを参考に、Growlが「受信される通知を聞く」、「リモートアプリケーション登録を許可」するように環境設定パネルから設定する。

gem類の準備

バージョンも併記した。

まずはgemの準備。

$sudo gem install ZenTest # 4.1.1
$sudo gem install redgreen # 1.2.2, autospecの結果をコンソールで赤と緑で塗り分けてくれる。Growlだけなら不要
$sudo gem install ruby-growl # 1.0.1

RSpecとRSpec on Railsのインストール。

ruby script/plugin install git://github.com/dchelimsky/rspec.git -r 'refs/tags/1.2.7' # 1.2.7
ruby script/plugin install git://github.com/dchelimsky/rspec-rails.git -r 'refs/tags/1.2.7.1' # 1.2.7.1
ruby script/generate rspec

画像を拝借

cd ~
curl http://blog.internautdesign.com/files/rails_fail.png > .rails_fail.png
curl http://blog.internautdesign.com/files/rails_ok.png > .rails_ok.png

moroさんが勉強会で使ってたチェックとバツマークのが欲しいんだけど、どこにあるんだろう?

~/.autotestの編集

ZenTestをインストールしたときについてくる設定ファイルのひな形をコピー。 x.x.xはインストールしたZenTestのバージョンで、ここでは4.1.1。

$ cp /Library/Ruby/Gems/1.8/gems/ZenTest-x.x.x/example_dot_autotest.rb ~/.autotest
$ chmod 644 ~/.autotest

以下のように設定する


# -*- ruby -*-

# require 'autotest/autoupdate'
# require 'autotest/once'
# require 'autotest/rcov'
# require 'autotest/restart'
# require 'autotest/timestamp'

# Autotest::AutoUpdate.sleep_time = o
# Autotest::AutoUpdate.update_cmd = o
# Autotest::RCov.command = o
# Autotest::RCov.pattern = o

ok_img = "~/.rails_ok.png"
ng_img = "~/.rails_fail.png"

module Autotest::Growl
  def self.growl title, msg, img=ok_image, pri=0, sticky=""
    msg += " at #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}"
    # autotestは使わないので、-nで指定するアプリケーション名はautospecで良いと思う
    # -Hで通知先のGrowlのあるホスト名を指定する
    system "growlnotify -n autospec -H localhost --image #{img} -p #{pri} -m #{msg.inspect} #{title} #{sticky}"
  end

  Autotest.add_hook :ran_command do |at|
    results = at.results.last
    examples = results[/(\d+)\s+examples?/].to_i  # テストの総数
    failures = results[/(\d+)\s+failures?/].to_i  # 失敗の数
    errors = results[/(\d+)\s+errors?/].to_i # エラーの数
    if examples >= 0
      if failures > 0 || errors > 0
        growl "Tests Failed", "#{examples} examples, #{failures} failures, and #{errors} errors", ng_img, 2
      else
        growl "Tests Passed", "#{examples} examples, #{failures} failures, and #{errors} errors", ok_img, -2
      end
    else
      growl "Tests Errored", "errors", ng_img, 2
    end
  end
end

実行

$ cd RAILS_ROOT
$ autospec

備考

いくつかサイトを巡って試行錯誤を繰り返したけど、とりあえず以上の作業のみで動作している。結構古い情報もあるみたいだし、autotestとautospecでは~/.autotestでテストの結果を拾う処理に差異があるのでご注意を。

ところでgrowlnotifyでは-Hオプションで通知先のGrowlのホスト名を指定しているけど、うまくやれば,別サーバ上で作業しているときのテスト結果をローカルのGrowlに通知なんてこともできるのかな?ちょっと余裕があるときにでも試してみたい。

参考サイト

変更履歴

2009-08-19

  • 30行目の行末からカンマ(,)を削除。
  • RSpec/Rspec on Railsのインストール時にバージョン指定を追記(これがないとtrunkをインストールしてしまう)。

2010-02-13

  • 成功.失敗時の画像ファイル名を変数で指定するように修正

14
Jun 09

rake db:createでキャラクタセット関連っぽいエラー

MacPortsでインストールしたMySQL5を使用。

rake db:createで次のようなエラーが出た。

Couldn’t create database for {“reconnect”=>false, “encoding”=>”utf8″, “username”=>***, “adapter”=>”mysql”, “database”=>”TestApp_development”, “host”=>”localhost”, “pool”=>5, “password”=>***, “socket”=>”/opt/local/var/run/mysql5/mysqld.sock”}, charset: utf8, collation: utf8_general_ci (if you set the charset manually, make sure you have a matching collation)

stick stackの記事BACCASS Labsの記事を参考に、/etc/my.cnfを作成してデフォルトの文字セットを設定:

sudo cp /opt/local/share/mysql5/mysql/my-medium.cnf /etc/my.cnf
sudo mate /etc/my.cnf
[client]
default-character-set = utf8

[mysqld]
default-character-set = utf8
collation-server=utf8_general_ci
character-set-server=utf8

[mysqldump]
default-character-set = utf8

[mysql]
default-character-set = utf8

追記 @ 6/15

上を実行してもrake db:createに失敗してた。というかそもそもmysqldが走ってなかった!かといってsudo launchctl start org.macports.mysql5を実行しても、”なにそれ?”的なエラーが返ってきた。

仕方が無いので、MySQLを再インストール:

$ sudo port uninstall -f mysql5
$ sudo port clean mysql5
$ sudo port install mysql5 +server
$ sudo launchctl load -w /Library/LaunchDaemons/org.macports.mysql5.plist
$ sudo -u mysql mysql_install_db5
$ sudo launchctl start org.macports.mysql5 

ps ax | grep myしてみると、どうやらプロセスは走ってるみたい。でもrake db:createには同様のエラー。

やんばるくいなの記事を参考に、上記のものに加えてmy.cnfにさらに次を追加:

[mysqld]
skip-character-set-client-handshake
init-connect = SET NAMES utf8

そしてmysqldを再起動:

$ sudo launchctl stop org.macports.mysql5
$ sudo launchctl start org.macports.mysql5

これでやっとrake db:createに成功!

ついでにエンジンをInnoDBに変更した:

[mysqld]
default-storage-engine = InnoDB
#そしてinno_から始まる行をuncomment

13
Jun 09

Unicodeな絵文字

docomoは基本がE63E-E6BA、拡張がE70C-E757。

auはタイプDは…調べるのがめんどうくさい。

SBMはE001-E05A、E101-E15A、E201-E253、E301-E34D、E401-E44C、E501-E537。

こっちのが見やすい?:i-modeezSBM

追記

JpmobileのJpmobile::Emotion::UTF8_REGEXPを使ってmatchしたほうが楽、かも


21
Mar 09

QTKitPlayerにフレームを画像として保存する

QTKitPlayerで再生中の動画の、現フレームを画像として保存する方法。ここではpngに限定してる。MovieDocument.hに次のコードを追加。

- (IBAction)doSaveCurrentFrameAsImage:(id)sender
{
    NSSavePanel *savePanel;

    QTTime qtCurrentTime = [mMovie currentTime];
    float currentTime = qtCurrentTime.timeValue / qtCurrentTime.timeScale;

    NSString *baseName = [[[self fileName] lastPathComponent] stringByDeletingPathExtension];
    NSString *fileName = [NSString stringWithFormat:@"%@-%07.2f.%@", baseName, currentTime, @"png"];

    // init
    savePanel = [NSSavePanel savePanel];

    // run the export sheet
    [savePanel beginSheetForDirectory:nil file:fileName modalForWindow:mMovieWindow modalDelegate:self
                       didEndSelector:@selector(saveCurrentFrameAsImagePanelDidEnd: returnCode: contextInfo:) contextInfo:nil];
}

    - (void)saveCurrentFrameAsImagePanelDidEnd:(NSSavePanel *)sheet returnCode:(int)returnCode contextInfo:(void *)contextInfo
{
    if (returnCode == NSOKButton) {

        // 現在のフレームの画像をNSImageで取得
        // 本来の画像サイズで取得するためにオプションにQTMovieNaturalSizeAttributeを追加
        NSArray *keys = [NSArray arrayWithObjects:QTMovieFrameImageType, QTMovieFrameImageSize, nil];
        NSArray *objects = [NSArray arrayWithObjects:QTMovieFrameImageTypeNSImage, [mMovie attributeForKey:QTMovieNaturalSizeAttribute], nil];
        NSDictionary *attrs = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
        NSImage             *image = [(NSImage *)[mMovie frameImageAtTime:[mMovie currentTime] withAttributes:attrs error:NULL] retain];

        // 画像データの変換
        // < http://www15.plala.or.jp/NovemberKou/programming/2ndGeneration/2ndHome/ImagePanelController/createPictureData.html >
        NSBitmapImageRep    *bitmapImageRep = [NSBitmapImageRep imageRepWithData:[image TIFFRepresentation]];
        NSDictionary        *properties = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
                                                                      forKey:NSImageInterlaced];
        NSData *pngData = [bitmapImageRep representationUsingType:NSPNGFileType
                                                       properties:properties];
        // export
        if (![pngData writeToFile:[sheet filename] atomically:YES])
            NSRunAlertPanel(@"Error", @"Error saving current frame as image.", nil, nil, nil);

        [image release];
    }
}

ちなみにwindowControllerDidLoadNib:あたりで[[mMovieView window] setContentAspectRatio:contentSize];をしておけば、QTMovieViewのアスペクト比も固定されるので便利。


13
Mar 09

universalchardetをCocoaで

Mozillaのエンコーディング判別ライブラリをCに切り出したuniversalchardetを、Cocoaなフレームワークにしてみた。

1 kB程度あればほぼ確実に判別できるので、NSStringのinitWithContentsOfFile:usedEncoding:error:の代わりに使えそう。

でもやっぱり数文字だとミスは増える。

ASCIIに収まる文字のみで構成されている場合には、1 kB程度あっても判別できていない、というかASCII互換なエンコーディングから適当なエンコーディングを選ぶことができない(Shift_JISとして保存された文章だけどASCII部分しか使われていない、という可能性)。

まぁ実用的には自動判別で読めればいいわけだから、適当に返せばいいのかも。というわけで、initWithContentsOfFile:usedEncoding:error:の代替メソッドを作るなら、

  1. まずinitWithContentsOfFile:usedEncoding:error:で読んでみる。UTF-16、32なら判別可能だし、OS 10.5で保存された文章であればファイルシステムの拡張属性から保存時のエンコーディングを読み取ってくれる。
  2. universalchardetで判別し、判別できたらinitWithContentsOfFile:encoding:error:で読み込む。
  3. 判別できなかったらUS-ASCIIのみで構成されている可能性があるので、NSASCIIStringEncodingとしてinitWithContentsOfFile:encoding:error:で読み込んでみる。
  4. 読み込めなかったら、諦める?

というような流れかな?ただしテキストファイルであるとわかっていることが前提(そうでない場合、当然universalchardetで判別できない)。

ちなみに、NKFでNSDataを拡張するNKFCocoaなるものがあるのか。


13
Nov 08

Carbonの新APIによる、Finder経由でのファイル削除

以前はAppleScript経由で面倒っぽかったけど、LeopardからCarbonにAPIが加わったらしい。

CarbonCore Framework Release Notes for Mac OS X v10.5


追記 @ 2009-03-23

とりあえず以下のコードで動くっぽい。要<CoreServices/CoreServices.h>。

- (BOOL)moveFileToTrash:(NSString *)filename
{
    FSRef fileRef;

    // 対象の存在をチェック
    if(![[NSFileManager defaultManager] fileExistsAtPath:filename])
        return NO;

    // FSRefの取得(ちなみにコードを拝借したCocoaDVDPlayerではUTF-8ではなくASCIIにしてた)
    //const char *cPath = [filename UTF8String];
    // 追記その2参照
    const char *cPath = [filename fileSystemRepresentation];
    OSStatus result = FSPathMakeRef ((UInt8*)cPath, &fileRef, NULL);
    if (result != noErr)
        return NO;

    // ゴミ箱に移動
    // オプションはテキトー。Finderのような効果音は別途処理を記述する必要あり
    result = FSMoveObjectToTrashSync(&fileRef, NULL,  kFSFileOperationDefaultOptions);

    return (result == noErr);
}

追記その2 @ 2009-03-28

Cocoa Study Blogの記事のコメントによると、UTF8StringではなくfileSystemRepresentationにすべきだろうとのこと。確かに、現在はUTF-8が使われてるけど、将来的には変更される可能性もあるし、こっちを使った方がいいかも。


27
Sep 08

GTK+ on OSX

Mac OS XでGTKアプリをビルドできる「GTK+ on OSX」 – マイコミジャーナル

例えばこれを使ってビルドされたGIMPの起動にはX11が必要なくなる、ということらしい。

ただしMac OS X 10.4 (Intel) が必要。