yoshida_eth0の日記 このページをアンテナに追加 RSSフィード

2016-11-05

設定ファイルを暗号化する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

2014-03-17

光の天使スクリーンセーバーから動画部分のみを取り出したい 備忘録その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に解凍出来ないとなんとも。

 

参考URL

SWFの構造要約/SWF ヘッダ - Flash SWF Spec

http://hkpr.info/flash/swf/index.php?SWF%E3%81%AE%E6%A7%8B%E9%80%A0%E8%A6%81%E7%B4%84%2FSWF%20%E3%83%98%E3%83%83%E3%83%80

2014-03-14

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

由結ちゃんかわいい!

f:id:eth0jp:20140314004843p:image

 

前提

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

 

参考URL

飯田來麗ちゃん応援スレ

http://anago.2ch.net/test/read.cgi/geinoj/1330311366/26

2014-03-06

さくら学院のデータを管理するライブラリ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.

2013-04-17

Rails3でPUTメソッドのリクエストを受けるとレスポンスが204に強制されてしまう件

204ステータスは、内容なし。

リクエストを受理したが、返すべきレスポンスエンティティが存在しない場合に返される。

Rails3ではなぜかPUTメソッド(GET / POST以外)だと204ステータスに強制されてしまい、コンテンツを返せない。

本当は200ステータスでコンテンツも返したい。

 

200ステータスでコンテンツを返す方法

ActionController::Responder.api_behavior を書き換える。

initializes辺りに以下ソースを入れておく。

module ActionController
  class Responder
    def api_behavior(error)
      raise error unless resourceful?

      if get?
        display resource
      elsif post?
        display resource, :status => :created, :location => api_location
      elsif resource.present?
        display resource
      else
        head :no_content
      end 
    end 
  end 
end

 

GET / POST以外のメソッドの場合に204を返すようにしている処理は、

actionpack-3.2.13/lib/action_controller/metal/responder.rb

で定義されていた。