TwitterのBootstrapを使ってみた

社内用の管理ツールを組み立てる際に、せっかくだからとBootstrapを使ってみました。

楽ですね。マークアップしていくだけで済むので非常に楽です。ドキュメントもわかりやすいのでやりやすいです。

デスクトップアプリの様なUI(を実現するJSフレームワークなど)については、面倒臭さが激増するだけだという偏見を持っているので、こういうフレームーワークは大歓迎です。

CSSを避け続けた自分にも、それなりのページを組み立てることが出来るなんて!!(棒

ありがたいことです。

Twitter APIの認証画面がSafariで英語になる件

  • SafariはAccept-Languageにja-jpをつける
  • Twitterはjaには対応するがja-jp(ja-JP)には対応してない
  • RubyGemのOAuthを使って認証処理を実装している

という状況です。

Safaritwitter.comにアクセスすると、何もしてなければおそらく英語ページが表示されるはずです。その後、フッターの言語選択で日本語を選択すると、以降は日本語で表示されます。

ここで使われているのが?lang=jaです。なので、lang=jaをつければいいんでしょ、と。OAuth::RequestToken#authorize_urlがクエリストリングにつけたいパラメータをHashで受け付けているので、そこに:lang => 'ja'とでも渡せばよいですね、と。

ちなみに、環境は以下の通り。

rubyのIOとエンコーディングについて

IOのドキュメントより

IOオブジェクトは外部エンコーディングと内部エンコーディングを持つ。

外部エンコーディング
IOが表すファイルなどの文字エンコーディング
内部エンコーディング
IOから読み込まれた文字列、あるいはIOの書き込みメソッドへ渡す文字列の文字エンコーディング

Kernel.#openのドキュメントより

第二引数のモードには、文字列では以下のパターンを受け付けている。

  • "{mode}"
  • "{mode}:{ext_enc}"
  • "{mode}:{ext_enc}:{int_enc}"

modeはr/w/a/r+/w+/a+というオープンモードの指定。ext_encは外部エンコーディングの指定で、int_encは内部エンコーディングの指定。

File.open

Shift_JISで記述したCSVファイルをエンコーディングを指定してFile.openで読み込んだ場合。

$ cat encoding.rb
File.open('sjis.csv', 'r:Shift_JIS:UTF-8') do |input|
  input.each_line do |line|
    puts line.encoding
    puts line
  end
end
$ ruby -v
ruby 1.9.2p290 (2011-07-09 revision 32553) [x86_64-darwin11.0.1]
$ ruby encoding.rb
UTF-8
"あ","い","う","え","お"

期待通り、Shift_JISで読み込んだ文字列をUTF-8に変換して出力してくれる。

CSV.foreach

CSV.foreachの:encodingオプションも同じだと思い試したが、例外が発生してしまう。

$ cat csv_encoding.rb
require 'csv'

CSV.foreach('sjis.csv', :encoding => 'Shift_JIS:UTF-8') do |row|
  puts row
end
$ ruby csv_encoding.rb
/Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:2027:in `=~': invalid byte sequence in UTF-8 (ArgumentError)
        from /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:2027:in `init_separators'
        from /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:1570:in `initialize'
        from /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in `new'
        from /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:1335:in `open'
        from /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb:1201:in `foreach'
        from csv_encoding.rb:3:in `<main>'

CSVライブラリのソースを追いかけて、読み込みの実装を確認。改めて、File.openの確認のために以下を試す。

io = File.open('sjis.csv', 'r:Shift_JIS:UTF-8')
data = io.read
puts data.encoding
io.rewind
data = io.read(1024)
puts data.encoding
io.close

バイト数を指定せずにreadを実行すると内部エンコーディングに変換されるが、バイト数を指定するとASCII-8BITとなる。

で?

どうしたらいいんだ?File#readの仕様を把握してないので、次はそこを探ろう。

追記

引数 length が指定された場合はバイナリ読み込みメソッド、そうでない場合はテキスト読み込みメソッドとして 動作します。

なるほど。

追記

CSVの読み込み処理。

class CSV
...
  def read_io(bytes)
    @io.read(bytes).force_encoding(raw_encoding)
  end
end
  1. バイナリ読み込みメソッドで読み込んでいる(変換されない)
  2. force_encodingで内部エンコーディングを強制している(変換してないのに)
  3. この状態のまま、=~でパターンマッチングを実行してinvalid byte sequence in UTF-8って言われる

追記

CSVモジュールのread_ioメソッドを書き換えて、エンコーディング指定だけじゃなく変換までやるようにしてみた。どうするのが筋が良いのかは考えてない。

--- /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb    2011-10-19 00:23:20.000000000 +0900
+++ /Users/koshigoe/.rbenv/versions/1.9.2-p290/lib/ruby/1.9.1/csv.rb.orig       2011-10-19 00:23:18.000000000 +0900
@@ -2315,7 +2315,7 @@
   end

   def read_io(bytes)
-    @io.read(bytes).encode(raw_encoding, @io.external_encoding)
+    @io.read(bytes).force_encoding(raw_encoding)
   end
 end

外部エンコーディングだけ指定した場合、内部エンコーディングも指定した場合で期待通りの結果を得られた。

require 'csv'

CSV.foreach('sjis.csv', :encoding => 'Shift_JIS') do |row|
  row.each{|col| puts col.encode('UTF-8') }
end

CSV.foreach('sjis.csv', :encoding => 'Shift_JIS:UTF-8') do |row|
  puts row
end

次はRedmineで情報を探るべきだろうか。見逃される様なものじゃないと思うし、バグではないと思うんだけどよくわからんね。

node.jsでFacebookアプリの開発を始める

FacebookのGraph APIやOpen Graphをもっと触っておこうというのが趣旨です。

なんでnode.jsを選んだか

根拠はありません。イベント駆動モデルということで、1インスタンスでのスループットRailsより高いのかなと思ったからです。
加えて、触ったことがない技術をいくつか触れるからという理由もあります。

環境はHeroku

収益を上げられるわけでもなく、利用も見込めない(herokuドメインSSL証明書を使うので自分以外は使いたくないはず)ので、Herokuの無料で使える範囲に収めたら良いと思っています。

  • Dyno x 1
  • Worker x 0
  • Cron /Day
  • MongoLab (240MB)

MongoLabだけ無料で使える容量が飛び抜けて大きいのが気になっています。なぜでしょうね?

MongoDB

これは、Herokuでそれなりの容量をもらえる無料ストレージがMongoDBだったという理由以外にありません。
敢えて言うなら、こういう場合でもない限り触る機会もなさそうだからということもあります。

node.js用にはmongooseを選択。他に選択肢があるのかは知りません。

Facebook

ひとまず、認証用にeveryauthを選択しました。connect-authやmongoose-authという選択肢もあるようです。

  • mongoose-auth
    • everyauthをMongoDBと一緒に使う場合に便利だとか
    • everyauthどころかnode.jsでいっぱいいっぱいなのに、さらにややこしくしないでくれ、ということで採用見送り
  • connect-auth
    • connectってなんだ、expressでこい、という超上から目線でマニュアルを求めた結果採用見送り

近況

  • node.js インストール
  • npm インストール
  • package.json 把握(HerokuでGemfile的な扱い)
  • mongoose経由でMongoDBを使ってみた
  • mongooseを使ったモデル定義を外部ファイルに切り出してみた
  • mongooseのfind系で躓いた
    • node.jsのイベント駆動モデルに慣れず、コールバック(非同期処理)で考える癖がついてない
    • findOneが結果を戻り値を返さないことにも手間取った
      • コールバック(クロージャ)で外側のローカル変数を共有して代入する方法でごまかしてる
    • そもそもCommonJSを把握してないので、Promiseとかなんとなくで使っているからフワフワする
  • everyauthでFacebook認証を実現してみた
    • node.jsかexpressのミドルウェアの仕組みを正しく理解してないため、ミドルウェアの挿入位置次第でうまく動かないことに中々気づけなかった
      • 順番次第でrequest.session.authがセットされずにrequest.userの準備が行われないことがある(findUserByIdがよばれない)

とにかく、「動けば良いや」の精神でドキュメントは読みつつ、あまり深入りせずに実装をトライ&エラーですすめています。

FacebookのOpen Graph

触ってみた。

使用感。

  • アプリケーション(名前空間)でのアクションとオブジェクトの準備が面倒くさい
  • 開発・ステージ・本番といった環境別にアプリケーションを作る場合に面倒くささが倍々ゲーム?
  • アクション、オブジェクト、アグリゲーションの設定項目が多いのが面倒だが、前向きに考えれば可能性が広がりんぐ
  • アクションはアプリケーションローカルしかないんだろうか?
  • オブジェクトにはグローバルなものがある(OGPで定義済みのog:typeはグローバルと思われる / book は名前空間不要だった)
  • アクションにせよオブジェクトにせよ、共通語彙に収束してくれるとやりやすくなりそう
  • publish_actionsパーミッションが要求された(アプリケーションにパーミッション与える前)

使うだけなら面倒なだけで難しさはありません。これを使ってキラーアプリ(?)作れと言われたら黙り込みますけどもね。

「アクション定義はアプリケーションごとに行う」ということに引っかかりを覚えたまま、消化不良でおしまい。

社内勉強会でUnicornについて発表したけどあまりの準備不足に全俺が泣いたので少しずつまとめていく〜中断

気が抜けてきたので、ここまで書いてきたことをHikiにまとめて中断とした。再開予定は未定。

社内勉強会でUnicornについて発表したけどあまりの準備不足に全俺が泣いたので少しずつまとめていく〜その6

Unicornの設定ファイル

Unicornは、Rubyスクリプトを設定ファイルとして読み込み評価(eval)する。

$ unicorn --config-file=unicorn.conf.rb config.ru

設定ファイルは指定された場合だけ評価される。--config-fileオプション(もしくは-c)が省略されれば、デフォルトの設定で動作する。

instance_eval(File.read(config_file), config_file) if config_file

設定ファイルの読み込みと評価は上記の通りで、Unicorn::Configuratorクラスのインスタンスをコンテキストとしてevalする形になっている。つまり、設定ファイルで利用可能なディレクティブは、基本的にUnicorn::Configuratorクラスのインスタンスメソッドと同義。

チューニングに関するドキュメントも書かれている。

設定ファイルのサンプルも用意されている。

以降、ディレクティブについて書き連ねていく。

before_exec/before_fork/after_fork

フックが用意されているので、必要に応じてコールバック処理を定義すると良い。

before_exec
新しいマスタプロセスを作る(reexec)際のexec()が実行される直前にあるフックポイント。
before_fork
ワーカープロセスをforkする直前にあるフックポイント。
after_fork
ワーカープロセスをfork後、いくつかの必要な処理を挟んだところにあるフックポイント。

after_forkでワーカープロセス個別にリスナーを登録することで、ワーカープロセスにダイレクトにリクエストを投げることができるので、デバッグなどで役立つことがあるはず。

preload_app

ワーカープロセスをforkする前に、マスタプロセスの中でアプリケーションをプリロードできる様にする。デフォルトで無効になっている。

この機能を有効にすることで、Copy-on-Writeフレンドリであればメモリ消費を抑えられる。ただし、マスタプロセスとワーカープロセスとの間でリソースを共有してしまう問題があるため、フックを利用してソケットを開き直すなどすること。ログファイルについては、アトミックな書き込みが出来るようにしているため、開き直す必要はない。

この機能が無効である場合、HUPシグナルによって設定ファイルをリロードした際にアプリケーションもリロードし、ワーカープロセスをグレースフルに再起動する。アプリケーションがロードされる際、常にGem.refreshが実行される。

user

ワーカープロセスのユーザとグループを指定することが出来る。指定しない場合は、マスタプロセスと同じものとなる。

ほか
  • client_body_buffer_size
  • listen
  • logger
  • pid
  • rewindable_input
  • stderr_path
  • stdout_path
  • timeout
  • worker_processes
  • working_directory