設定ファイルを暗号化するSecureConfってライブラリを作った

手軽に設定ファイルをRSAで暗号化するライブラリ。
平文をそのまま保存しないという大義名分をミニマムに果たせる。
IDとかパスワードとかをうっかり平文のままコミットしてしまうのを防ぐ為に。
 

作るきっかけ、困ってた事

誰かが発行してくれるTokenを使ってなんやかんやするサービスだったら、設定ファイルを別に作ってgitignoreに追加しておけばいいかなと思う。
けど、普段使ってるID/Passを使ってWebベースで外部サービスにログインしてクロールしたり自動化したりゴニョゴニョしたりする場合、設定ファイルを別に作ったとしても平文では入れておきたくない。
かと言ってわざわざRSAのキーペア作って暗号化とかするのも面倒だし、うっかりキーペアをgit addしたりしてしまう人なので作業用ディレクトリに置く事自体がアレだったりする。
要はキーを生成・管理したくないけど、暗号化して平文をうっかりコミットしてしまうのは防ぎたい。
 

作ったもの

キーを生成・管理したくないからデフォルトでは ~/.ssh/id_rsa を使って暗号化する。
Yamlとかの設定ファイルをHashとして扱うライブラリで、要素名が「enc:」から始まれば暗号化をする。
値は、Marshalでシリアライズ出来るものであればなんでも扱える。
 

sample
path = File.expand_path("config.yml", File.dirname(__FILE__))
config = SecureConf::Config.new(path)

config["enc:id"] = "yoshida-eth0"
config["enc:pass"] = "himitsu"
config["last_access"] = Time.now.to_s
config.save

p config["enc:id"]
p config["enc:pass"]
p config["last_access"]

 

生成されるYamlファイル
---
enc:id: a9NhqDVi6JkjDQ89AisLzX153HoLU0zx/dbBb3XWE0uXXRcdwGKnFNQi7iXy3k5zM3DBWU/B4sJ2OWRLjNahKNzYUuQcfuSpRx4iStSzTFw6riLIsSokctT80GWYt8cUxcHQeApaKjI7Dwd+1zRUr2Txs+gYOIcItPPZ3oIeHItuQRnMN1wca7wxRW9unvmnXgui6WIoBJ+mH39deBM80UP57ssLBr7mYdd5xL8v9U3Xgs4802mvEDOuyLeP0sRx/okv4S0zhpzyUfYWRxtetP8WmDa6OyB/IkxLVIF+mLlmlPV800JZovbNExRCB5ibwRV/f+Y0Daa3WWIUjt3p/A==
enc:pass: 08sO8IsLuDmR6S+BEk4MzVk6OFRRhZ9A1BJT8U60Y9I/WBMZYfwKfI5lUzrSR3YjTO2raK/kQHpq4oYwSXkTBVeGf3shhzCqzPDjILkYfy2S+hq35gbOnc4v2eu2oo0g5uTMGq/coZ9mIhyUUPaf2dE0aseEIM3AfUKbDLAsrEWFaNVQVgQnmj9q4PKBBYX21UMDwfZ8jezdeOEJanbN81U6wYFCydbkJMUTPCLL5XvhtNpF3nYCc2dgUpjC5dFOGeYUAb9vnHX6/1LDSHHFQtg/bjG9OqpmDPOa4+hZa8xfefSxDJ7+fEhbWW0RlxbbnaFhHjBhs1pS4oz+m3mT/w==
last_access: '2016-11-05 22:19:58 +0900'

 

インストール

$ gem install secure_conf

 

あとがき

デフォルトでの使い方だと複数台で使う事を考えてない個人用途だけど、ちゃんと使いたければ色々出来るからソース読んでください。
 

URL

secure_conf | RubyGems.org | your community gem host
https://rubygems.org/gems/secure_conf
 
yoshida-eth0/ruby-secure_conf
https://github.com/yoshida-eth0/ruby-secure_conf

Play Frameworkを使ってみる

インストールからアプリ作成まで。
 

前提

Mac
JDK 1.7が入ってる。
 

環境構築

まずはインストール。

sudo su -
cd /tmp/
curl -O http://downloads.typesafe.com/play/2.2.1/play-2.2.1.zip
unzip play-2.2.1.zip
mv play-2.2.1/ /usr/local
cd /usr/local
ln -s play-2.2.1 play

export PATH=$PATH:/usr/local/play

 

プロジェクト作成

testappというプロジェクトを作成する。

cd /tmp/
play new testapp
cd testapp/

 

サーバ起動

以下コマンドで http://0.0.0.0:9000 でサーバが起動する。

play run

 

Eclipseからインポートする

以下コマンドでEclipse用のファイルが生成され、インポート出来るようになる。
各々の環境にあわせて生成されるので、ここで生成されるファイルはGit等では管理しない方がいい。

play eclipse

 

ライブラリの追加

build.sbt に依存ライブラリを追記する。
とりあえずTwitter4Jを追加してみる。
サーバを起動すると追加したライブラリが勝手にダウンロードされる。

name := "testapp"

version := "1.0-SNAPSHOT"

libraryDependencies ++= Seq(
  jdbc,
  anorm,
  cache,
  "org.twitter4j" % "twitter4j-core" % "3.0.5"
)     

play.Project.playScalaSettings

 
IDEからライブラリのクラスパスを解決するには以下を再度実行。

play eclipse

光の天使スクリーンセーバーから動画部分のみを取り出したい 備忘録その1

その2があるか解らないけど、とりあえず備忘録その1。
Objective-C、SWFファイルフォーマット、ActionScript、Zlib辺りの知識が必要になりそうな。
 

概要

fla:verを使ってる

fla:verを使って、Flashファイルからスクリーンセーバーを生成していた。
仕組みは、WebViewでResources/data.pacに入ってるSWFファイルを再生しているっぽい。
 
光の天使とは関係ないけど、このfla:verをビルドしているのはユーザ名「maruyama」。
fla:verを作成している株式会社シリアルゲームズの社員紹介を見ると丸山国明さんという方がいらっしゃるようなので、おそらくこの方が製作者もしくはリリース者。
 
fla:ver -スクリーンセーバー開発作成ツール フレーバー
http://flaver.jp/
 
株式会社シリアルゲームズ|ソーシャルゲーム/スマートフォンアプリ/ゲームサーバ/WEBサービス開発
http://www.serialgames.co.jp/
 

メモリリークしてる

システム環境設定でスクリーンセーバ選択画面を表示し続けているとFlash Playerのメモリ使用量がどんどん増えていく。
fla:verが悪いのかSWFファイルが悪いのかは不明。
 

HTTP

User-Agent

認証の為のHTTPリクエストは、User-Agentを見るにWebViewからリクエストされているっぽい?
ただ、fla:ver試用版を使った限り、HTTPリクエストをして認証する機能はついていない。
かと言ってActionScriptからのリクエストっぽくもない。

 @header=
  {"host"=>["www.hikarinotenshi12.jp"],
   "accept"=>["*/*"],
   "accept-language"=>["ja-jp"],
   "connection"=>["keep-alive"],
   "accept-encoding"=>["gzip, deflate"],
   "user-agent"=>
    ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.73.11 (KHTML, like Gecko)"]},

 

ソケットポリシーファイル

843/tcpに対してソケットポリシーファイルを取りに行っている様子はない。
そもそもソケットポリシーファイルはActionScriptでSocketを使う時に必要で、HTTPRequestするのには必要ないんだっけ?
忘れた。
 

Cocoa

Objective-Cの他にC++も使われているようだ。
いわゆるObjective-C++ってやつ。
 

使用されているフレームワークやライブラリ、ソースファイル等
/System/Library/Frameworks/Cocoa.framework/Versions/A/Cocoa
/System/Library/Frameworks/ScreenSaver.framework/Versions/A/ScreenSaver
/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon
/System/Library/Frameworks/Security.framework/Versions/A/Security
/System/Library/Frameworks/WebKit.framework/Versions/A/WebKit
/usr/lib/libz.1.dylib
/usr/lib/libstdc++.6.dylib
/usr/lib/libgcc_s.1.dylib
/usr/lib/libSystem.B.dylib
/usr/lib/libobjc.A.dylib
/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices
/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation
/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/FlashSaverUtils.mm
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/FlashScreenSaverView.mm
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSGeometry.h
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSRange.h
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/FSSWindow.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/SGClickableImageView.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/SGClickableTextField.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaver/src/SGMG2Codec.mm
/usr/lib/libmx.A.dylib
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/src/FlashSaverUtils.mm
/Developer/SDKs/MacOSX10.4u.sdk/System/Library/Frameworks/CoreFoundation.framework/Headers/CFByteOrder.h
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/../FlashScreenSaver/src/FlashScreenSaverView.mm
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/src/FSSWindow.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/src/SGClickableImageView.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/src/SGClickableTextField.m
/Users/maruyama/work/devel/Flaver/FlashScreenSaverPPC/src/SGMG2Codec.mm

 

宣言されているメソッド
+[FlashSaverUtilsPro14 GetURLReqByPath:]
+[FlashSaverUtilsPro14 GetResDirectory:]
+[FlashSaverUtilsPro14 CreateOutputDirectory:]
+[FlashSaverUtilsPro14 CreateFixedOutputDirectory:]
+[FlashSaverUtilsPro14 CreateFixedOutputDirectory:extentionFlag:]
+[FlashSaverUtilsPro14 RemoveFileFromArray:]
+[FlashSaverUtilsPro14 OpenSettingPList:]
+[FlashSaverUtilsPro14 GetSwfStageRect:]
+[FlashSaverUtilsPro14 GetSwfRect:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) createFlashView:preview:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) unloadFlashMovie]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) loadBlank]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:identifierForInitialRequest:fromDataSource:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:resource:didFinishLoadingFromDataSource:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) viewViewTree:indent:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:resource:didFailLoadingWithError:fromDataSource:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:plugInFailedWithError:dataSource:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:decidePolicyForNavigationAction:request:frame:decisionListener:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:decidePolicyForMIMEType:request:frame:decisionListener:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:createWebViewWithRequest:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) webView:contextMenuItemsForElement:defaultMenuItems:]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) getFlashVersion:]
-[FSV_915233D19F2543E045B67F0ABE91F22A initWithFrame:isPreview:]
-[FSV_915233D19F2543E045B67F0ABE91F22A dealloc]
-[FSV_915233D19F2543E045B67F0ABE91F22A setFrameSize:]
-[FSV_915233D19F2543E045B67F0ABE91F22A startAnimation]
-[FSV_915233D19F2543E045B67F0ABE91F22A stopAnimation]
-[FSV_915233D19F2543E045B67F0ABE91F22A drawRect:]
-[FSV_915233D19F2543E045B67F0ABE91F22A animateOneFrame]
-[FSV_915233D19F2543E045B67F0ABE91F22A isRightClickMenu]
-[FSV_915233D19F2543E045B67F0ABE91F22A isIgnoreKey]
-[FSV_915233D19F2543E045B67F0ABE91F22A setFinishSaver]
-[FSV_915233D19F2543E045B67F0ABE91F22A sendMouseMoved]
-[FSV_915233D19F2543E045B67F0ABE91F22A hasConfigureSheet]
-[FSV_915233D19F2543E045B67F0ABE91F22A uninstallSaver:]
-[FSV_915233D19F2543E045B67F0ABE91F22A sheetCancelDidEnd:returnCode:contextInfo:]
-[FSV_915233D19F2543E045B67F0ABE91F22A uninstallAllUserSaver:]
-[FSV_915233D19F2543E045B67F0ABE91F22A sheetCancelDidDismiss:returnCode:contextInfo:]
-[FSV_915233D19F2543E045B67F0ABE91F22A closeSheet:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseMoved:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseEntered:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseExited:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseDown:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseDragged:]
-[FSV_915233D19F2543E045B67F0ABE91F22A mouseUp:]
-[FSV_915233D19F2543E045B67F0ABE91F22A scrollWheel:]
-[FSV_915233D19F2543E045B67F0ABE91F22A rightMouseDown:]
-[FSV_915233D19F2543E045B67F0ABE91F22A rightMouseUp:]
-[FSV_915233D19F2543E045B67F0ABE91F22A keyDown:]
-[FSV_915233D19F2543E045B67F0ABE91F22A keyUp:]
-[FSV_915233D19F2543E045B67F0ABE91F22A flagsChanged:]
-[FSV_915233D19F2543E045B67F0ABE91F22A configureSheet]
-[FSV_915233D19F2543E045B67F0ABE91F22A(local) loadFlashMovie]
-[FSSWindow becomeKeyWindow]
-[FSSWindow setConfigWindow:]
-[SGClickableImageView12a dealloc]
-[SGClickableImageView12a SetClickable]
-[SGClickableImageView12a resetCursorRects]
-[SGClickableImageView12a SetLinkedURL:cursorImage:]
-[SGClickableImageView12a SetUsingSafari:]
-[SGClickableImageView12a mouseDown:]
-[SGClickableTextField12a initWithCoder:]
-[SGClickableTextField12a initWithFrame:]
-[SGClickableTextField12a dealloc]
-[SGClickableTextField12a isOpaque]
-[SGClickableTextField12a drawRect:]
-[SGClickableTextField12a getStringViewSize]
-[SGClickableTextField12a mouseEntered:]
-[SGClickableTextField12a mouseExited:]
-[SGClickableTextField12a mouseDown:]
-[SGClickableTextField12a SetLinkString:url:cursorImage:]
-[SGClickableTextField12a setStringValue:]
-[SGClickableTextField12a resetCursorRects]
+[SGMG2Codec encode:]
+[SGMG2Codec decode:]
+[SGMFile(local) getCStringTempDirPath:]
+[SGMFile file]
-[SGMFile init]
-[SGMFile dealloc]
+[SGMFile getTempDirPath]
+[SGMFile remove:]
+[SGMFile getFileType:]
+[SGMFile isFileType:path:]
+[SGMFile createTempDirectory:]
+[SGMFile getPropertiesDirectory]
+[SGMFile checkDirectory:create:]
-[SGMFile openTempFile:]
-[SGMFile write:]
-[SGMFile close]
-[SGMIgnoreRightClick init]
-[SGMIgnoreRightClick initWithTarget:]
-[SGMIgnoreRightClick dealloc]
-[SGMIgnoreRightClick getEventTime]
-[SGMIgnoreRightClick setEventTime:]
-[SGMIgnoreRightClick setTargetView:]
-[SGMIgnoreRightClick getTargetView]
-[SGMIgnoreRightClick isIgnore]
-[SGMIgnoreRightClick start]
-[SGMIgnoreRightClick stop]

 

FSV_915233D19F2543E045B67F0ABE91F22Aクラス

メソッド名から察するに、おそらくNSViewもしくはWebViewを継承している。
「FSV」はFlashScreensaverViewの略だろうか。
「915233D19F2543E045B67F0ABE91F22A」部分はスクリーンセーバー毎に生成されるようだ。
16進数32桁という事はMD5だろうか。
 
クラスの宣言はこんな感じだろうか。

@interface FSV_915233D19F2543E045B67F0ABE91F22A : NSView <WebResourceLoadDelegate, WebPolicyDelegate, WebUIDelegate>

 

Contents/Resources/data.pac

fla:verの試用版を使い、自分でもスクリーンセーバーを作って比較しつつ確認した。
 
このファイル全体は、暗号化や圧縮はされていない可変長のアーカイブファイルという事が解った。
ただ、tarやzipのようなメジャーなものではなさそう。
 
暗号化も圧縮もされていないので、CWS形式のSWFファイルのヘッダ「CWS」のindexを探してそこからバイナリをsplitする事で元のSWFと完全一致するバイナリデータを抽出出来た。
 
元のSWFファイルがある場合はいいが、今回は元のSWFファイルがない為、ファイルの終端がどこにあるか解らない。
その為、SWFファイルをCWSからFWSへZlib解凍を試みて、正しく解凍出来た箇所をファイル終端とする事にした。
しかし、終端を1バイトずつ削っていって全パターン試したが、どこで区切っても正しく解凍出来ない。
SWFファイルに問題があるか、よしだの頭がてんてこまいなのかのいずれかだと思われる。
 

SWFファイル抽出

そもそも「incorrect header check」と出るので終端の位置の問題ではないのかも知れない。
でも自分で作ったスクリーンセーバーでは正しく動作するので、少なくとも基本的な部分は間違っていなそう。

#!/usr/bin/env ruby

require 'zlib'

bin = File.open("Resources/data.pac"){|f| f.read}
bin.force_encoding("ASCII-8BIT")

# data.pacからSWFファイルの開始場所を探す
start = bin.index("CWS")
puts "CWS index: #{start}"
puts "max swfsize: #{bin.length - start}"

# 最大長のSWFファイル(CWS形式)を書き出す
cwf = bin[start..-1]
File.open("data_cws.swf", "w"){|f| f.write(cwf)}

# SWFバージョン
puts "SWF Version: #{cwf[3].ord}"

# SWFの解凍後のファイルサイズ
puts "SWF FileLength: #{cwf[4..7].unpack("V")[0]}"

# SWFファイルをCWSからFWSへ解凍
zbin = cwf[8..-1]
fws = Zlib::Inflate.inflate(zbin) # incorrect header check (Zlib::DataError)
puts "FWS FileLength: #{fws.length + 8}"

# SWFファイル(FWS形式)を書き出す
File.open("data_fws.swf", "w"){|f| f.write("FWS" + cwf[3..7] + fws)}

 

Flash

SWFをFWSに解凍出来ないとなんとも。
 

光の天使のスクリーンセーバーを今でも見られるようにする方法

由結ちゃんかわいい!

 

前提

Mac
Ruby 2.0以上が入っている。(多分1.8.7とかでも動くけど一応)
80ポートをApacheやnginxが握っていない。
root権限が使える。
 

実行

問題なければ以下実行。

su -

dscacheutil -flushcache
echo '127.0.0.1 www.hikarinotenshi12.jp' >> /etc/hosts
echo '127.0.0.1 hikarinotenshi12.jp' >> /etc/hosts

curl -O http://g-storage.appspot.com/share/eth0jp/hikarinotenshi12jp.rb
ruby hikarinotenshi12jp.rb

 

さくら学院のデータを管理するライブラリRubyGems::sakura_gakuinを公開した

さくら学院のメンバーやクラブ活動のデータを取得出来るライブラリ、sakura_gakuinをRubyGemsで先日公開した。
いつか作りたいと思っていたAcmeモジュール。(Rubyだけど)
 
sakura_gakuin | RubyGems.org | your community gem host
https://rubygems.org/gems/sakura_gakuin
 
yoshida-eth0/ruby-sakura_gakuin
https://github.com/yoshida-eth0/ruby-sakura_gakuin
 
install

gem install sakura_gakuin

 
シンプルな中からゲスさが見え隠れするRubyらしい綺麗なコードが書けたと自負してる。
アイドルがテーマのgemだしネタ扱いされて見向きもされないんだろうと勿体無く思う位には気に入ったコードが書けた。
なのでこのライブラリのお気に入りポイントを挙げてみる。
 

module SakuraGakuin::YamlLoadable

メンバーやクラブ活動のデータは全てconfig/*.ymlに格納してある。
このYAMLファイルを使う各実装クラスは SakuraGakuin::YamlLoadable をincludeする。
このモジュールは以下の処理をする汎用的な共通モジュール。
 
YAMLファイルを読み込んでObjectの配列へ変換しクラス変数へキャッシュする。
・プライマリキーの生成。
・カラムに対してindexを作成。
・indexを用いた検索。
・Enumerableの実装。
 
SakuraGakuin配下に置かなくてもいいんじゃないかって位汎用的。
今ライブラリの肝になるモジュール。
 

YAMLでのデータバインディング

データの読み込みは、module SakuraGakuin::YamlLoadable の yaml_load メソッドにて行う。
 
このYAMLファイルでデータバインディングの為の記述をしてあって、ファイルを読み込むと同時にオブジェクトに変換してくれる。
Memberクラス/ClubMemberクラス/Clubクラスを読み込むタイミングでYAMLファイルを読み込み、クラス変数にキャッシュするようにした。
 
YAMLのデータバインディングは、セッターを介さずに直接インスタンス変数に突っ込まれる。
なのでクラスにセッターを定義しなくても問題なく動く。
シンプルで綺麗。
 

クラスとリレーション

クラスのインスタンスを相互にリレーション出来るようにした。
これらは全てO(1)での検索が可能。(後述)
O(1)に対応していないリレーションは割愛。
結構数があるので、ソースかドキュメントを参照。
 
クラスとリレーションは以下の通り。
 

SakuraGakuin::Member

さくら学院のメンバー。
名前や身長等のデータが含まれる。
 
・Member.club_members
 インスタンスメソッド。
 Memberに紐付くClubMemberの配列を返す。
 

SakuraGakuin::Club

さくら学院のクラブ活動。
クラブ活動の名前、グループ名のデータが含まれる。
 
・Club.club_members
 インスタンスメソッド。
 Clubに紐付くClubMemberの配列を返す。
 

SakuraGakuin::ClubMember

MemberとClubを多対多で紐付けるクラス。
ActiveRecordで言う所の、through で指定するクラスみたいなものだが、
ここにはクラブ活動固有の名前(YUIMETALや堀内研究員等)や入部した日、退部した日のデータも含まれる。
 
・ClubMember.club
 インスタンスメソッド。
 ClubMemberに紐付くClubを返す。
 
・ClubMember.member
 インスタンスメソッド。
 ClubMemberに紐付くMemberを返す。
 

SakuraGakuin::YamlLoadable

・YamlLoadable.[]
 特異メソッド。
 includeしたクラスのidに紐づくインスタンスを返す。
 

ID系のデータにindexを張ってO(1)で検索・リレーション出来るようにした

具体的には、以下のデータにindexを張った。
・Member.id
・Club.id
・ClubMember.id
・ClubMember.club_id
・ClubMember.member_id
 

idの生成

idというカラムはYAMLファイルには記述していない。
これは module SakuraGakuin::YamlLoadable の primary_key メソッドにカラム名を1つ以上渡して、IDカラムを生成する。
例えば、Member.idは、firstname_enとlastname_enを合わせてIDとしている。
 
YamlLoadable関連の処理は、クラス定義の先頭に書きたい。
ActiveRecordでhas_manyを先頭に書きたいのと同じで、気分的にヘッダー情報として上の方に書きたい。
しかし yaml_load してすぐに primary_key しようとすると、firstname_en等のゲッターがまだ記述されていない為NoMethodErrorが起きてしまう。
その為、idは必要になったタイミングで遅延評価して生成するようにした。
module SakuraGakuin::YamlLoadable の primary_key メソッド内で define_method(:id, lambda) している。
これでidカラムの問題は解決した。
 

id以外のindex作成

id以外のindexを張るのは、module SakuraGakuin::YamlLoadable の create_index メソッドを使う。
この中でindex生成用のlambdaと、そのlambdaによって生成されたデータをそれぞれ持つようにした。
これで実際に検索が行われるまで何もしないようになり、ゲッター定義前にcreate_index出来るようになった。
 
index生成用のlambdaをこう定義される。

@indexes[indexに使うカラム] = -> (indexの値){}

 

indexを用いた検索

Rubyのlambdaは、[]メソッドで実行出来るのでとても都合がいい。
indexを用いた検索は以下で行う。

@indexes[:club_id][:baton_club]

これが呼ばれた時、初回1回だけ :club_id に対するindexを作成し、2回目以降はO(1)で引っ張ってこれる。
まるで多段Hashを操作しているような感覚で、遅延実行されたデータを取得する事が出来る。
実際の検索は、module SakuraGakuin::YamlLoadable の index_search メソッド等で行われている。
 
遅延評価をさらっとソースに埋め込むのはモテコードだと噂で聞いた事がある。
おそらくこれはそのモテコードに準ずるものなのではないかと。
 
しかし、O(1)で動作するリレーションには時間の情報が含まれない。
例えば以下コードは、現在在籍中のメンバーではなく歴代メンバー全員を返す。
もし在籍中のメンバーのみを取得したい場合は select(&:active?) 等を使う。

irb(main):014:0> SakuraGakuin::Club[:cooking_club].club_members.count
=> 6
irb(main):015:0> SakuraGakuin::Club[:cooking_club].club_members.select(&:active?).count
=> 3

 

クラブ活動でのメンバーの名前

重音部と科学部については、メンバーの名前が変わる。
その為、ClubMemberに名前を持たせ、クラブ活動特有の名前が存在しない場合はMember.nameを見るようにした。

irb(main):017:0> SakuraGakuin::ClubMember[:cooking_club_yui_mizuno].name
=> "水野由結"
irb(main):018:0> SakuraGakuin::ClubMember[:heavy_music_club_yui_mizuno].name
=> "YUIMETAL"

 
クッキング部ではメンバーカラーが存在するが、それは追加していない。
メンバーカラーのない他のクラブ活動でabstractのような状態になってしまう為、ポリモーフィズムを優先させる。
 

Timecopで「今」を変える

さくら学院が成長期限定ユニットだからこその問題がある。
前述のとおり、時間の変化に伴い返るべき値が変わる。
その為デフォルト引数を Date.today としているメソッドが多く存在している。
しかし毎度毎度日付を指定するのは非常に面倒。
 
そこで登場するのがTimecop。
以下のコードで2011年度卒業式当日のメンバーと当時の年齢の一覧を出力出来る。
すなわち、会長が引っ張って来てくれて、それを支えてる三吉がいて、隣で笑ってるあいりーんがいる時代のメンバーの一覧である。

irb(main):019:0> Timecop.travel(Date.new(2012, 3, 25)) do
irb(main):020:1*   SakuraGakuin::Member.select(&:active?).map do |member|
irb(main):021:2*     "#{member.name}(#{member.age})"
irb(main):022:2>   end
irb(main):023:1> end
=> ["武藤彩未(15)", "三吉彩花(15)", "松井愛莉(15)", "中元すず香(14)", "堀内まり菜(13)", "飯田來麗(13)", "杉崎寧々(13)", "佐藤日向(13)", "水野由結(12)", "菊地最愛(12)", "田口華(12)", "磯野莉音(11)"]

 

TODO

以下余力があったらやる事リスト。
 

生徒会長、トーク委員長、部長等の役職の追加。

これらも時間の変化に対応する必要があり、且つ複数掛け持つ可能性も考えなければならない。
生徒会長については、年度の切り替わり時期に不在となる。
 

その他

テストケース作成。
RDocコメント記述。
 

これ系のライブラリ

アイドル系は、モー娘、AKB48ももクロの3つ。
Ruby実装はプリキュア1つ。
探した感じだとこんな感じ。
今回は以下のライブラリを参考にしつつも、1から作った。
 

Perl

Acme::MorningMusume リリース - delirious thoughts
http://blog.kentarok.org/entry/20050404/1112585090
 
Acme::PrettyCure - JPerl Advent Calendar 2010 Acme Track
http://perl-users.jp/articles/advent-calendar/2010/acme/6
 
Acme::AKB48 - JPerl Advent Calendar 2010 Acme Track
http://perl-users.jp/articles/advent-calendar/2010/acme/7
 
第一回ももクロハッカソンに参加して Acme::MomoiroClover リリースしました - 2nd life
http://secondlife.hatenablog.jp/entry/20110904/1315129581
 

PHP

Acme_MorningMusume \ パッケージ \ Openpear
http://openpear.org/package/Acme_MorningMusume
 
Acme_IdolMaster \ パッケージ \ Openpear
http://openpear.org/package/Acme_IdolMaster
 

Ruby

Rubyプリキュアを作った #cure_advent - くりにっき
http://sue445.hatenablog.com/entry/2013/12/16/000011
 

あとがき

コントリビュータ募集中

特に水野由結ちゃん卒業後(2015年4月以降)にアップデートしていくかは現状定かではなく、メンテナンスされない可能性もあります。
お気軽にPullRequestください。
 

英語

Gemの説明文を英語で色々考えたけど、正しいかは不明。
間違ってるよ!とかもっといい言い回しあるよ!と思った方はご指摘 or PullRequestください。

All about a limited growth period only Japanese idol Sakura Gakuin.
All about Sakura Gakuin, the Japanese idol limited for growth period.
All about a Japanese idol Sakura Gakuin, that consists of girls of up to junior high.
All about Sakura Gakuin, that this Japanese idol consists of girls of up to junior high.
All about Sakura Gakuin, that this Japanese idol consists of girls of growth period.
All about Japanese idol "Sakura Gakuin", that consists of limited to growth period girls only.

mruby-oauthをビルドする

探り探りな感じで。
 

mruby

何はともあれmrubyをビルドしてみる。

$ git clone https://github.com/mruby/mruby.git
$ cd mruby
$ rake

 

mgem

まずはRubyでいう所のgem、mgemを入れる。

$ gem install mgem

 
インストールすると、 ~/.mgem ディレクトリが生成される。
 
~/.mgem/GEMS_ACTIVE.lst => mgem add したgem名が追加されるファイル。
~/.mgem/mgem-list => mgemで入れられるパッケージの情報が入ってるディレクトリ。
 
これ、どうやらプロジェクト毎の設定ではなくユーザ毎の設定になるらしい。
ちょっとイケてない。
 
mgemの追加はこんな感じ。
依存関係は解決してくれない。
大分イケてない。

$ mgem add mrugy-oauth

 
必要なパッケージを追加し終えたら設定を出力。

$ mgem config default > build_config.rb

 

結局こんな感じ

build_config.rb

mruby-oauthを使うだけでこれだけの依存関係があるらしい。
Mac環境ではこれ。
それ以外の環境ではもっと増えるかも知れない。
Linux/Unix以外ではmruby-uvが必要らしい事を@matsumoto-rさんに教えて頂いた。

MRuby::Build.new do |conf|
  toolchain :gcc

  conf.bins = %w(mrbc mruby mirb)

  # mruby's default GEMs
  conf.gem 'mrbgems/mruby-math'
  conf.gem 'mrbgems/mruby-struct'
  conf.gem 'mrbgems/mruby-time'
  conf.gem 'mrbgems/mruby-sprintf'

  conf.gem 'mrbgems/mruby-bin-mruby'
  conf.gem 'mrbgems/mruby-bin-mirb'

  # user-defined GEMs
  conf.gem :git => 'https://github.com/iij/mruby-digest.git'
  conf.gem :git => 'https://github.com/mattn/mruby-http.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-httprequest.git'
  conf.gem :git => 'https://github.com/iij/mruby-io.git'
  conf.gem :git => 'https://github.com/mattn/mruby-json.git'
  conf.gem :git => 'https://github.com/iij/mruby-mtest.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-oauth.git'
  conf.gem :git => 'https://github.com/iij/mruby-pack.git'
  conf.gem :git => 'https://github.com/luisbebop/mruby-polarssl.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-simplehttp.git'
  conf.gem :git => 'https://github.com/matsumoto-r/mruby-sleep.git'
  conf.gem :git => 'https://github.com/iij/mruby-socket.git'
end

 

参考URL

人間とウェブの未来 - mruby-oauthでmrubyからtwitterを操作
http://blog.matsumoto-r.jp/?p=3195
 
Twitter
https://twitter.com/matsumotory/status/421788045235343360
 

追記 2013/01/12 23:24:57

これが現在最新のbuild_config.rbとの事。
https://gist.github.com/matsumoto-r/7783929

レシートが邪魔だから効率的に捌く術を考える

数ヶ月前まで支払いは殆どクレジットカードだったのに、ここ最近は現金払いが増えてレシートが凄く邪魔。
これを効率的に捌く術を考え中。
 

紙の現金出納帳

現在の手法。
面倒な上に不便。
 

Zaimのレシート読み取り

精度が悪い。
 

現金出納ツールを自作

ツールとしての機能よりも、自分が今欲しい最低限の機能に特化したパターン。
無料のWebサービスも出回ってるし、多分作っても面白くない。
 

会計ツールを自作

現金以外のものも管理出来るものを。
自分が使いたいかどうかよりも、ツールとしての機能を重視したパターン。
 
仕訳から貸借対照表やら損益計算書やらを自動作成とか。
これ系ならMapReduceしやすそう。
数字も整数だから精度的にも問題ない。
64bit integerにすれば万人に対応出来るだろう。
 
ただ、作るのも使うのも非常に面倒臭い。
 
作る面倒ex)
手数料とかを考慮すると、仕訳と借方・貸方が一対多の関係になる。
この時点で面倒になってる。
 
使う面倒ex)
ちゃんとやると、クレジットカードで買い物をした時の仕訳は、
購入時に未払金が発生し、引き落とし時に未払金が消滅する。
 

まとまってないけどまとめ

せっかく商業高校を出てるんだし、少しは活かしたい気持ちもある。
ただ、今やりたい事もやらなきゃいけない事も多め。
そうこうしているうちにどんどんレシートが溜まっていく…。
どうしたもんかな。