ほぼ電子書籍に移行した話

ほぼ電子書籍に移行して2年くらい経ちました。 ここ1年で紙の本は数十冊しか買ってないと思います。 ほとんどはkindleで、一部の本とエロ本はニコ書で買ってます。 ログを見ると去年1年でkindleで1200冊くらい買ってるようです。 紙で買っているのは最新な技術書、しばらく電子化されないことが確定している漫画、それと毎月の快楽天くらいです。

ほぼ完全に電子書籍に移行した結果の利点・欠点・変化などをまとめます。

利点: 部屋が狭くならない

家には本棚が4本あって、すべて本が詰まっていて、もうこれ以上本棚を置く場所がありません。 これまで紙の本を買うときは、保管をどうするかという罪悪感を感じながら買っていました。 結局置く場所がなくて床に山積みになっていて、足の踏み場がないという状態です。 こんな状態でも電子書籍なら何も気にすること無く購入できます。

逆に電子書籍で買い直しが進んだ結果、本棚1本分くらいは処分できそうです

利点: いつでも買える

ポチってダウンロードされるのをちょっと待つだけですぐ読めます。 革命的な便利さです。 amazonの普及により本屋に行くというダルいことをする必要はなくなり、これも十分に革命的でしたが、 人間は怠惰なので、今となってはamazonで注文した荷物を受け取るということがダルくてたまりません。 最近では、受け取ることを考えると億劫でポチる手を躊躇するということもあります。 電子書籍だと荷物を受け取るダルさも回避できるので、あとは本を読むダルさを何とかするだけです。 これに関しては買っても読まないという革命的なソリューションがあります。

さらにいうと、一部の本は紙と電子同日発売していますが、 同日発売と言っても一日のスタートは深夜なので実店舗の開店や宅配便の到着より電子書籍の方が圧倒的に早く買えます。

利点: 積ん読しやすい

買っても読まないソリューションは完璧ですが、それでも気まぐれに買った本を読みたくなることがあるでしょう。 紙の本が床に積まれている場合、それはスタック構造になっています。 同じスタック上に同じシリーズの本が2冊積まれてしまった場合、古い巻が下に、新しい巻が上に積まれることになります。 紙は意外に結構重いので非力な人間ではスタックの途中から読みたい本を抜くことはできません。 この状態ではそのシリーズはもう2度と読み進むことができないことになります。 一方電子書籍はランダムアクセス可能なので、どれだけ積ん読しても任意の巻から読むことができます。

利点: 軽い

技術書なら1冊分よりNexus7のほうが軽いです。 漫画でも2-3冊でNexus7のほうが軽くなります。 その軽い端末の中に数日間は活字に困らない数百冊の本が入ります。 必要なのはNexus7を入れるのに十分なサイズのケツポケがあるジーパンです。 端末ドリブンのジーパン選びは本当に重要です。

利点: オナニーしやすい

Flash版ニコ書プレイヤーに搭載されたFPSキーバインドにより、 右手が開放され非常にオナニーしやすくなっています。

欠点: 画質が悪い

小さな出版社ほどモワレが酷い・・・ 酷い・・・

欠点: 知らない本に出逢いにくい

kindleでもまだ全然レコメンドが生きてない感じします。 知らなかった漫画と偶然出逢う機会が紙の頃よりだいぶ減っている実感があります。 これの対処のために週に何度かとらのあなに行って面白そうな本を探してます。 面白そうな本があったら新刊netに登録してkindle版発売を待ちます。

欠点: 無限に買える

物理的な制約から解き放たれた物欲ヤバイ

欠点?

世間一般で欠点だと言われてそうなこと

  • 電子化されるのが遅い
    • 気にしなければおk
  • 貸し借りできない
    • 友達いねえよバーカ
    • 買えばいいだろ
  • 紙の暖かみがどうのこうの
    • そんなものは幻想だ
  • プラットフォームが死んだらどうなんの?
    • まともなプラットフォームなら別サービスに移行できるように作ってるでしょ
    • 安心して買えばおk
  • 販売停止がありうる
    • 覚悟のあるプラットフォームを選べ
    • エロ本はニコ書で買ってます
  • 目が疲れる
    • どうせ寝てる時以外はモニタ見てんだから一緒だろ

変化

  • たぶん買う量は増えた
  • 読む量も増えた
  • 本を入れるカバンを持たなくてもよくなった
  • 部屋はこのあと片付く予定

番外編: 電子書籍を仕事にして良かったこと

電子化されない本の情報が入ってくるので紙の本を買う決断が素早くできる点です。

さて、amazonから届いたあまんちゅの最新刊を読んできます。

超チューニング祭に参加した

ニコニコ超会議3のアサインされたブースは超時空ニコニコ研究所だったはずなんですが、 ブース説明会に参加して1時間半ほど真面目に説明を聞いていたところ、 最後の最後に「4人はハッカソン行ってね」と突然言われて「ファッ!?」ってなったわけです。

というわけで超チューニング祭に参加してきました。

やったこと

成果物

フロントエンドの専門知識があるわけでないので 事前に指摘されていたように、 特にフロントを弄ってスピードが改善されるような予感は全くありません。 デザインもできないので提供されたデータ一式には一切手を付けないことにしました。 なので配信周りで速くすることを目論みました。 (全員こっちの方は最低限やってくると思ったのに意外にほとんどの人は全くやってなかった)

方針

  • 配信を速くする
  • その処理をスクリプトで自動化する
  • 元ファイルの構造は弄らない
  • 現実に即した複数ページに応用可能なチューニング方法
  • 計測には http://tools.pingdom.com/fpt/ を使用

css と js を1ファイル化

rails の assets pipeline がやっていることそのままです。 html をそのまま使うということで、明らかな罠である遅いサーバから配信されている jquery もあえてそのまま使います。 単純に html をパースして出てくる js と css を前から順に concat していくだけです。 リクエスト数は減らせましたが結局多いのは画像なのであまり意味ないです。

html と css と js を minify

rails では js の minify を uglifier を使っているのですが、css では何を使っているのか分からなかったので、 両方できるっぽい yui-compressor を使ってminifyしました。

html はもともと html5 だったので不要なタグをだいぶ削除できます。 h5-min という凄く古い gem を多少修正したら最新の ruby でも動作しました。

それぞれファイルサイズを20%程度削減できました。

この html の minify の段階で元ソースの文法的に間違った部分が悪影響を及ぼし、 レギュレーションを満たさなかったようです。

具体的には

1
2
3
<a href="/hoge">
 <p>hoge</p>
</a>

がアウトだったぽいです。

よく考えたらフロントのチューニングなのにブラウザのレンダリング結果は一度も見てなかったような気がします。

AWS で配信

とりあえず assets を s3 にアップロードしました。 これだけでだいぶ速くなります。 デプロイ先のサーバがだいぶアレだったぽいです。

そのあと CloudFront 経由でアクセスできるようにしました。 この効果は絶大で圧倒的に速くなりました。 pingdom の計測結果によると、Connect と Wait にかかる時間が圧倒的に削減されたようです。

ただ、Chrome の Developer Tool で見た場合、 s3 そのままのほうが全体的に速いという結果も出ました。 だいぶ謎いです。

配信ドメインを増やす

ブラウザには1つのドメインに対する同時接続は2までという紳士協定が(一応)あります。 CloudFront のドメインをたくさん作ってラウンドロビンすることで同時ダウンロード数を増やしました。 計測結果によると、1ドメインの場合、後半になるにつれて Connect にかかる時間が長くなっていたのが、 複数ドメインの場合ほとんど変化がないことが分かります。

ただ、ブラウザ全体(またはページ単位)での同時接続数もあるらしく、 増やしすぎてもすべて同時ダウンロードというわけにはいきませんでした。 1ドメインの時の Connect もそれほど遅いわけではないので、ほとんど誤差と言えると思います。 期待したほどの効果はありませんでした。

HTTP compression

css と js を圧縮して配信という昔ながらの方法は試してみましたが、 CloudFront での設定が面倒だった上に、 Receiving は短くなったものの Waiting が長くなって、合計値は素の状態より遅くなりました。 なぜ Waiting に計上されるのわからないけどこれがファイルを展開する時間なのでしょうか? ネットワークが十分速い現代はもはや HTTP compression の必要が無いのかもしれません。

できなかったこと

画像を sprite 化すればかなり効果的だと予想できますが、 正方形に近くなるように詰め込むアルゴリズムでないと、 無限に非効率になってしまいそうです。 それを考えている時間はなかったので諦めました。

結論

RailsとAWS使え

費用

ファイル数も少ないし2日間の合計で アクセス数 35,500 転送量 250MB で1ドルもかかりませんでした。 ドメイン作りまくっても追加で費用がかかることもないようです。

感想

異種格闘技戦だけど自分のフィールドに持ち込んである程度戦えたような感じします。 結局計測方法の詳細がわからないので再検証できないのが辛いですねー。

あと優勝したデザイナの人のスライド見ても全然意味が分からなかったりしたのが衝撃的。

非エンジニアのためのOAuth講座

OAuthって何?

いわゆるこういうのです

最近Twitterで スパムアプリがおっぱいポロリと勝手につぶやく といった事件が世間を賑わせています。 このままでは「OAuthは犯罪です!」なんて言い出す人が現れかねないので、 OAuthってどんなものなのか、Twitterを例に誤解されそうなところを簡単に説明したいと思います。

誤解1: OAuthはユーザ認証の機能である

多くのWebサイトに Twitterでログインする 機能があり、 上の画面をユーザ認証の仕組みだと思っている人は多いと思いますが、 大間違いです

OAuthは、他のアプリ(サイト)に対して、自分の代わりにTwitterにアクセスする権利を与える仕組みです。 その過程でユーザ情報がとれるので、ログインの代わりに使っているアプリが多いのですが、本来は間違った使い方です。 他のサービスを使ってユーザ認証をする仕組みに OpenID というOAuthの兄弟規格のようなものがあるのですが、 対応しているサービスも少なくイマイチ流行ってません。

何はともあれOAuthの許可をすると、Twitterの操作をする権限をアプリに与えてしまします。 信頼できないアプリに対して許可なんか与えたらダメです。 何ができるかは許可画面に書かれているので よく読みましょう 。 上の画像の場合は、

  • tweetが読めるよ
  • フォローしてる人が見れるよ
  • プロフィール変更できるよ
  • tweetできるよ

と書かれています。

Twitterは親切で何ができないかも書いてあります。

  • ダイレクトメッセージにはアクセスできないよ
  • パスワードは見れないよ

どこまで権利を欲しがるかはアプリによって違うので きちんと読みましょう

まとめ

  • OAuthは他のアプリに権限を渡す仕組みだよ
  • 許可画面はよく読もう
  • 信頼できないアプリを許可したらダメだよ

誤解2: 念のためパスワード変えたほうがいい

スパム系アプリが流行すると、パスワード変えたほうがいいという人が必ず現れますが、全く不要です。 むしろパスワード変えないとヤバイよと煽って来る人は、 罠にかけようとしている可能性もあるので警戒したほうがいいです

まともなサービスなら、OAuthで許可を得ていても アプリからパスワードは見えないし変更もできません 。 そもそもパスワードは元の文字列に復元できない方法でデータベースに保存されています。

アプリとサービスは、OAuthの許可を与えた時に発行される アクセストークン という文字列を使ってやりとりします。 なのでアプリはパスワードを一切知ることはないし、知る必要もありません。 もしアプリ側でパスワードを入力させようとしてきたら 完全に罠です

アプリの許可を取り消せば、アクセストークンが無効化されて、それ以降アプリはアクセスできなくなります。 あとは勝手にされたtweet消したり勝手にされたフォロー外したりは手動でやる必要があります。

まとめ

  • パスワードは変えなくていいよ
  • アプリがパスワード入力を求めてきたら罠だよ
  • 許可を取り消せばそれっきりだよ
  • ビビって騙されないように

誤解3: OAuthは安全である

条件が揃えば、結構簡単にアプリのなりすましが可能です。(特にスマートフォンアプリ)

アプリにはいくつかOAuthのための設定がありますが、 それを全く同じにして別のアプリを作ると、本物になりすますことが可能です。

信頼できるアプリを許可をしたあと、自動でアプリに戻って来るタイミングで、 複数のアプリ候補 (Androidの場合 他のOSは知らん) がある場合は、その中に偽物が含まれていると思っていいでしょう。 まだこの時 アクセストークン は発行されていないので、ここでストップすれば乗っ取りは防げます。 しかし、もし間違って偽アプリを選んでしまったら、 偽アプリがアクセストークンを取得してしまいます。 つまりその瞬間になりすましが成立します。

アプリ選択画面が出てくるのはまだマシな方で、 もしこれが、もう使ってなくて アンインストールしたアプリ と同じ設定を使っていた場合、 アプリ選択画面すら出ずに 偽アプリがアクセストークンを取得してなりすまします。 過去に許可していて、その許可がまだ有効な場合は 許可画面すら出ずに 偽アプリがなりすますこともあります。

Webアプリの場合はスマートフォンアプリほどなりすましは簡単ではないですが、 ドメインごと盗まれた場合 はなりすまし可能です。 信頼できないDNSを使っている場合 もなりすまし可能です。 サービスを運営する会社が 買収された時 は買収会社の動きに注目したほうがいいです。

なりすまされた場合、許可したアプリ一覧に出てくる名前は 信頼できるアプリ なので、 何が犯人なのかなかなか気づきにくいです。

まとめ

  • アプリ候補が複数出たら怪しいからストップしたほうがいいよ
  • アプリを消したら許可も取り消そう
  • 定期的にリストをチェックして使ってないアプリは全部取り消そう
  • 犯人がわからない時はアプリの許可を全部取り消せばとりあえず問題解決

さいごに

OAuthは、ユーザIDやパスワードを漏らすこと無く、他のアプリにアクセス権を与える仕組みで、 過去にあった IDとパスワードを保存して代理アクセスするアプリ に比べたら 格段に安全 な手段です。 エンジニア視点からしても、知らなくていいこと(パスワード)を知らないままでいられる 非常に便利な仕組みで、 今後さまざまなサービスがOAuthでのアクセスを提供するようになると思います。 そういう時代なので上記の内容を最低限知っておいたほうがいいと思います。

ただやはり気軽に許可できるものではないので、アプリを許可する際はよく読んでよく考えてよく疑ってかかりましょう。

あとOpenIDは事実上死んでいるので 何もできないOAuth として再定義されるべきだと思います。

山城が死んだ

飯田橋のカフェを貸し切って山城のお別れ会。

LTしたり、花火打ち上げたり、カネ貸したまま返ってこない暴露話をしたり、 山城の恥ずかしいツイートを見ながら遺品山分けゲーム大会したり、 MBAでスイカ割ったり、MBAにベルギービール飲ましたり、 クズが集まってクズなどんちゃん騒ぎをして最高に楽しかった。

ただ、 楽しい場所には必ずいるはずの奴がいない。 いつも輪の中心で延々イジられ続けてるはずの奴がいない。 哀しい。 いたのかもしれないけど生きてる人間には見れない。 哀しい。

つーか死んでんじゃねえよバカ

あと俺が死んだ時もLT大会で盛大に送ってほしいと思った

Gemの作り方まとめ 普通のgem編

会社の人にgemの作り方まとめてくれって言われたので標準的なgemの作り方をまとめます。 標準的な作り方なので他の人が作ったgemを読み解くヒントにもなります。

とはいえ有名なgemは(有名なgemに限って)メッチャクチャだったりするので読みづらかったりします。 歴史が古かったりすると特にね。

ジェネレータ

まずはジェネレータを使ってプロジェクトを作りましょう。 昔はいろいろあったけど最近は bundle コマンドで大勢が決定してる感じです。

bundle gem test_gem -t

-t はテストも作成するオプションです。デフォルトでrspecを使うようになってます。 その他のオプションは

bundle help gem

で確認できます。

Railsプラグインのgemを作る場合は

rails plugin new test_gem

を使う方法もあります。 別記事で詳しく書く予定です。

作成されるファイル

作られるファイルの一覧はこんなかんじです

 create  test_gem/Gemfile
 create  test_gem/Rakefile
 create  test_gem/LICENSE.txt
 create  test_gem/README.md
 create  test_gem/.gitignore
 create  test_gem/test_gem.gemspec
 create  test_gem/lib/test_gem.rb
 create  test_gem/lib/test_gem/version.rb
 create  test_gem/.rspec
 create  test_gem/spec/spec_helper.rb
 create  test_gem/spec/test_gem_spec.rb
 create  test_gem/.travis.yml

gem名について

gem名の - はディレクトリ構造に変換されます。 例えば test-gem という名前で作ると lib/test_gem.rb だったところが lib/test/gem.rb のような構造になります。

Gemfile 内で

1
gem 'test-gem'

が正しくロードできるように lib/test-gem.rb を作ってその中で

1
require 'test/gem'

するといいでしょう。

gemspec

gemの中を見たことがなければgemspecというファイルは初見でしょう。 gemspecファイルはgemのメタデータが書かれています。

生成された直後はこんな内容になっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'test_gem/version'

Gem::Specification.new do |spec|
  spec.name          = "test_gem"
  spec.version       = TestGem::VERSION
  spec.authors       = ["HOGE"]
  spec.email         = ["hoge@example.com"]
  spec.description   = %q{TODO: Write a gem description}
  spec.summary       = %q{TODO: Write a gem summary}
  spec.homepage      = ""
  spec.license       = "MIT"

  spec.files         = `git ls-files`.split($/)
  spec.executables   = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
  spec.test_files    = spec.files.grep(%r{^(test|spec|features)/})
  spec.require_paths = ["lib"]

  spec.add_development_dependency "bundler", "~> 1.3"
  spec.add_development_dependency "rake"
  spec.add_development_dependency "rspec"
end

まずTODOのところを埋めましょう。 spec.homepage にはgithubのurlを入れましょう。

このファイルで重要なのは依存するgemの設定です。 add_dependencyadd_development_dependency を使って必要なgemを追加しましょう。 Gemfileはこの中身を参照しているので変更する必要はありません。

それ以外の設定はなるべくいじらないほうがいいです。

中身の作成

まず lib/test_gem.rb を見てみましょう。

1
2
3
4
5
require "test_gem/version"

module TestGem
  # Your code goes here...
end

注目するのは TestGem が module だということです。 ここはなるべく module のままで、実際に色々やるのは下の class に任せましょう。

例えば Client という class を作るなら、 lib/test_gem/client.rb を作って

1
2
3
4
5
module TestGem
  class Client
    # codes
  end
end

な感じに書きます。 クラス構造とディレクトリ構造を一致させましょう。

それを lib/test_gem.rb でロードします。

1
2
3
4
5
require 'test_gem/version'
require 'test_gem/client'

module TestGem
end

ActiveSupportのautoloadを使う場合は、

1
2
3
4
5
require 'active_support/dependencies'

module TestGem
  autoload :Client, 'test_gem/client'
end

という感じになります。 Railsだと Gemfile に書いたgemを自動でrequireしてくれますが、ここではしてくれないので手動でrequireしなければいけません。

テストもディレクトリ構造を一致させましょう。 最初は spec/test_gem_spec.rb しかありません。 spec/test_gem ディレクトリを作り、 その中に spec/test_gem/client_spec.rb を作るという感じです。

guard を使う場合は、

1
2
3
guard :rspec do
  watch(%r{^lib/(.+)\.rb$})     { |m| "spec/#{m[1]}_spec.rb" }
end

と定義すればちょうどディレクトリ構造がぴったり合います。

実行可能なコマンドを含める

例えば rspec のような実行可能なコマンドも簡単に含めることができます。 bin ディレクトリを作り、その中に実行ファイルを入れるだけです。

コマンドなのでファイル名に .rb を付けないほうが見栄えがいいです。 例えば bin/run_test_gem というファイル名で

1
2
3
4
5
#!/usr/bin/env ruby

require 'test_gem'

p TestGem::Client.new

のような内容にすると、 run_test_gem というコマンドが使えるようになります。

ただし普段rvmを使っているので、これに関してだけは他のシステムだとどうなるかちょっと分からないです。 rvmの場合、直接コマンドを実行せずにラッパースクリプト経由になるので

1
TestGem::Client.new

だけで動いてしまいます。

ビルドとかリリースとか

ビルドやリリースはすべてrakeタスクが用意されています

rake -T

で確認しましょう。

リリースの前は lib/test_gem/version.rb を手で編集してバージョン番号を上げないといけません。 (これのせいでjewelerが好きだった)

上記の実行可能コマンドを実際に試してみたいときは、対象ファイルをコミットして

rake install

でインストールしないと実行できないというよくハマりがちな罠があります。

実際にどんなふうにクラス書いていけばいいの?

他のgemをたくさん読みましょう。

よく知られているgemの多くはとにかくデカイです。 構造を把握するだけでもかなり大変です。 小奇麗にまとまったgemを探して読んでみましょう。

一番大事なこと

コードにもコミットメッセージにも 日本語を含めるべきではない

もうちっとだけ続くんじゃ

  • Railsプラグインの作り方
  • C拡張を含むgemの作り方

不自由なデプロイを強いられてる人のための Capistrano-env ってgemを作った

capistrano-env ってgemを作った

不自由なデプロイを強いられているんだッ

すべての人間は大きく2種類に分割でき、それは自由なデプロイを許された人間と、不自由なデプロイを強いられた人間です。

不自由なデプロイを強いられる環境とは次のような環境のことをいいます。

  • 自動デプロイとかDevOpsとかの文化がない
  • 特定の人しかデプロイの権限がない
  • その特定の人が現代的なクラウド文化を知らない
  • 偉い人が手動神話を信仰している
  • システムを動かすのに絶対必須な情報を開発者が知ってはいけない

以上はすべて妄想ですがもしかしたらこんな組織が実在するのかもしれません。 世の中には非常に便利なデプロイツールがあるのにこのような組織では上手く使うことが出来ません。

このgemはこのような不自由なデプロイを駆逐し、自由なデプロイの尖兵として賭けの攻勢に出るための武器です。

秘密情報という曲者

組織によっては開発者が知ってはいけない情報が設定されている場合があります。 DBサーバのパスワードやSALTなど、まあこのへんは納得できるんですが、 自分たちのAPIサーバのドメイン名を知らなかったり、何台のサーバで動いてるのかすら知ってはいけない、 なんて組織がもしかしたらあるかもしれません。

このあたりの情報は普通 config/database.yml とかに書いてリポジトリにコミットするわけですが、 秘密情報を含むファイルは作れないのでコミットできないことになります。 以前はどのようにしていたかというと、 config/database.yml.template とかのファイルをコミットし、 ローカルやテスト環境ではそれを config/database.yml にリネームするし、 本番にデプロイするときはインフラ担当が 手動で 正しい情報に書き換えて配置します。

はいこんな思想で自動化できるわけがないですよね。 それどころかフレームワーク標準のファイルが存在しないという状況は開発者のCIすら阻害されてしまいます。

heroku的解決

設定をコードから分離するための仕組みとして、 herokuにはアプリケーションごとにconfigが設定できるようになってます。 アプリケーションからは環境変数を通してその値にアクセスできます。

1
heroku config:set FOO=123

こんなかんじに設定すると

1
ENV['FOO'] # => 123

でアクセスできます。便利! これなら同じコードを全く変更すること無く別のherokuアプリケーションとして動かすこともできますね。

capistranoでENV設定

capistranoでデプロイするときに、環境変数付きでサーバを起動することができます。

1
set :default_environment, ENV.select{ |k,v| k =~ /^MYAPP_/ }

とか書けば MYAPP_ から始まる環境変数を全部デプロイ時に渡すことができます。 アプリ側は ymlファイルの値が書いてある部分を <%= ENV['MYAPP_XXX'] %> とかに置き換えて、 ymlを読む前に一回 Erb を通すようにすれば完成です。

これでデプロイ担当者のマシンに環境変数を設定し、 そのマシンからcapistranoで自動化されたデプロイができるようになりました。

develop/test/staging なんかの環境は別に秘密じゃないとおもうのでymlに即値で書いてしまいましょう。

unicornリスタート問題

unicornは一瞬もサービスが途切れること無くサーバのリスタートができるのが特徴ですが、 これはシグナルを送ることによって実現しているので、 ENVを使った場合はリスタートしても設定の変更ができない という問題があります。

ENVを更新しようとすると一旦unicornをstopしてstartしなくてはいけません。 これではunicornの良さが損なわれるのでなるべくやりたくない方法です。

ここまで辿り着いた段階でもう既存の方法では厳しいと思いgemを作りました。

capisrano-env がやっていること

やってることは単純です。 環境変数を付けてデプロイするのをやめて、 代わりに 環境変数を設定するファイル(コード) を一緒にデプロイします。 unicornをリスタートする前にも環境変数ファイルを毎回デプロイします。 動かすアプリケーションがRailsの場合は、 勝手にそのファイルを読み込んでrailsのブート中に環境変数を書き換えます。 config/application.rb などでymlの読み込みを始める前にはすでに環境変数が設定された状態になっています。

環境変数を設定するコードを実行するっていう気持ち悪さはありますが、 不自由な環境にいながら自動デプロイで幸せになるためにはこの方法しか考えられませんでした。 まぁ不自由な環境が実在するかどうかは定かではありませんが。

使いかた

deploy.rb

1
2
3
4
5
require 'capistrano-env'

capenv.use do |env|
  env.add /^MYAPP_/
end

ここまで書いててこれ自分で設定しなくて default_environment 拾ってくればいいんじゃねえのと思った

あと環境変数を設定するコードを吐く部分はフォーマッタって形で切り出してるから PhpFormatter とか作りたい人作っていいよ

Java-ja Kowai

java-ja.DDD 行ってきました

java-ja 怖い

Make Url Cool

Railsでurlをカッコ良くする

railsで普通にこんな感じに config/route.rb を書くと、

1
2
3
resources :genres do
  resources :books
end

urlはこんな感じになります。

1
 /genres/1/books

1ってなんだよ! 1って!!

urlにidじゃないものをつかう

ジャンルのようなある程度数が決まっていて名前も変化しなさそうなのは、

1
 /genres/comic/books

みたいなurlにできるとカッコイイですよね。

genre_books_path(genre) のメソッドで、genre.idではなくgenre.nameにできれば上手くいきそうです。 このメソッドを調べると(追っていくのメチャメチャ難しかった)、 どうやら to_param というメソッドを経由して id が呼ばれていました。 このto_paramを書き換えてnameを返すようにしてしまっていいのでしょうか?

そこで http://api.rubyonrails.org でto_paramを探すと、「ActionPackでurlを作るときに使うよ!」と、 しかもまさにやりたいことそのままのサンプルコードが置いてありました。

というわけで to_param を書き換えてしまえばid以外にも自由にurlを作れる ことがわかりました。

to_param って・・・

url作るキーを取得するメソッドが to_param ってそんなの検索できねーよ・・・ 名付けがクソだと人生の貴重な時間を無駄にするという例でした。

本当にやりたいこと

1
2
 /comics
 /novels

みたいなurlにしたい。 resourcesをきちんと使って。

Run Ruby on Supervisor With RVM

supervisorとは

supervisorとは、pythonで書かれたプロセス監視ツールです。 簡単にインストールでき、デーモンとして動作し、 マシンブート時に任意のプログラムを立ち上げたり、そのプロセスが死んだ時に自動再起動などができます。

動かすプログラムは、単純な無限ループするようなコードでよく、 プロセス永続化などの面倒な仕事は全部supervisorに任せてしまえます。

RVMで使う時の問題点

RVMは個人環境にrubyをインストールし、実行するためには環境変数の整備などが必要で、 通常はシェルの起動時に設定スクリプトを読み込んで上手いことやってくれます。 ですが、supervisorを使う場合その方法が取れないのでなんとかしないといけません。

supervisorはenvironmentで環境変数を設定できるのですが、

1
$ rvm env

で出力した環境変数を使っても上手くいきませんでした。

1
command=which bundle

は想定したpathを返すのですが、

1
command=bundle exec ...

にすると bundle not found. と・・・

1
command=cd /path/to/project ; bundle exec ...

にしてみても、上手いこと解釈してくれないし、そもそもcdを見つけてくれません。 (シェル組み込みコマンドだから?)

もう意味わかりません。

そう言えば昔wrapperってあったよね

そういえば昔まだbundlerがなくてpassengerが主流だったときは、 rvm wrapper とかいうのやってそれを使えって書いてあるサイトが多かったと思います。 なんかマルっと環境を上手いことやってくれるものだっていう認識でした。 試してみましょう。

なんか適当に名前を付けてwrapperを作ります。 gemsetも一緒に hoge_task とかの名前にします。

1
$ rvm wrapper 1.9.3-p385@hoge_task hoge_task bundle

これで $HOME/.rvm/bin に hoge_task_bundle というコマンドができあがりました。 実際にフルパスでこのコマンドを叩くと hoge_task のgemsetを使ってbundleが動いているのがわかります。 supervisorのcommandの設定にこれを使えば良さそうです。

supervisorの設定ファイルは全体でこんな感じです

/etc/supervisor/conf.d/hoge_task.conf

1
2
3
4
5
6
7
8
9
[program:hoge_task]
command=/home/{USERNAME}/.rvm/bin/hoge_task_bundle exec rackup -p 8000
autostart=true
autorestart=true
stopsignal=QUIT
stdout_logfile=/var/log/hoge_task/out.log
stderr_logfile=/var/log/hoge_task/err.log
user={USERNAME}
diretory=/home/{USERNAME}/projects/hoge_task

あとは

1
2
$ sudo supervisorctl reload
$ sudo supervisorctl status

とかやって設定を読み込み直し、結果を見てみましょう。 多分うまく動き出してるはずですよ!

Gemfileやruby自体の更新

プログラムを変更してGemfileが変わった時や、 rubyのバージョンアップをした時などはどうなるのでしょうか?

これもGemfileのアップデートなら

1
$ bundle install

で、いつもどおりgemをインストールした後、(もちろん @hoge_task のgemsetを使って!)

1
$ sudo supervisorctl restart hoge_task

でプロセスを再起動すればいいはずです。

rubyを更新した場合は、新しいバージョンを指定してrvm wrapperを作りなおせば、 hoge_task_bundle の中身が書き換えられるので、supervisorの設定はそのままで大丈夫です。 あとは上と同様に bundle install してプロセスの再起動を指示します。

もう何も怖くない

これはsupervisorの設定と言うよりむしろ、 ユーザ環境にインストールされたrvmをシステムに近いところで使う汎用的な方法のようです。 自動でrackアプリを起動したり、バックグラウンドでタスクを回したり、いろいろできるようになりました。

というわけで仕事で使うメモでした。

Travis CIでrubyのC拡張が入ったgemのテストをする方法

No Such File

C拡張が含まれたgemをそのままTravisに流してみると、

$ bundle exec rake
56/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/bin/ruby -S rspec spec/jpeg_spec.rb
57/home/vagrant/builds/masarakki/jpeg/spec/../lib/jpeg.rb:2:
  in `require': no such file to load -- jpeg.so (LoadError)

自分でコンパイルしないといけないみたいですね。

とりあえずやってみた

試しに.travis.ymlでコンパイルの命令を追加してみました。

1
2
before_script:
- cd ext ; ruby extconf.rb ; make install

やった! Travisがテスト全部通したよ!!!

Travisの実行ログを見てみると、

55$ cd ext ; ruby extconf.rb ; make install
56checking for jpeglib.h... yes
57checking for main() in -ljpeg... yes
58creating Makefile
59gcc -I. -I. -I/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib/ruby/1.8/i686-linux -I. -DHAVE_JPEGLIB_H  -D_FILE_OFFSET_BITS=64  -fPIC -g -O2  -fPIC   -c jpeg.c
60gcc -I. -I. -I/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib/ruby/1.8/i686-linux -I. -DHAVE_JPEGLIB_H  -D_FILE_OFFSET_BITS=64  -fPIC -g -O2  -fPIC   -c jpeg_error.c
61gcc -I. -I. -I/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib/ruby/1.8/i686-linux -I. -DHAVE_JPEGLIB_H  -D_FILE_OFFSET_BITS=64  -fPIC -g -O2  -fPIC   -c jpeg_jpeg.c
62gcc -shared -o jpeg.so jpeg.o jpeg_error.o jpeg_jpeg.o -L. -L/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib -Wl,-R/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib -L.  -rdynamic -Wl,-export-dynamic    -Wl,-R -Wl,/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib -L/home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib -lruby -ljpeg  -lrt -ldl -lcrypt -lm   -lc
63/usr/bin/install -c -m 0755 jpeg.so /home/vagrant/.rvm/rubies/ruby-1.8.7-p358/lib/ruby/site_ruby/1.8/i686-linux



    _人人人人人人人人人_
    > site_ruby の下 <
     ̄^Y^Y^Y^Y^Y^Y^Y^ ̄

これはダメですねヤバイですねやっちゃダメですね。

rake-compiler を使う

rake-compiler というgemを使うと上手いことやってくれるようです。

gem追加

Gemfileに以下を追加

1
gem 'rake-compiler'

んで

$ bundle install

タスク追加

Rakefileに以下を追加

1
2
3
4
5
6
7
if RUBY_PLATFORM =~ /java/
  require 'rake/javaextensiontask'
  Rake::JavaExtensionTask.new('GEM_NAME')
else
  require 'rake/extensiontask'
  Rake::ExtensionTask.new('GEM_NAME')
end

rake-compiler流にディレクトリ構成を直す

ext直下にextconf.rbがある場合は、ext/GEM_NAME/extconf.rb の形になるようにディレクトリ構成を変更します。

ためしてみる

rake compile してみる

$ rake compile
mkdir -p tmp/x86_64-linux/jpeg/1.9.3
cd tmp/x86_64-linux/jpeg/1.9.3
/home/masaki/.rvm/rubies/ruby-1.9.3-p194/bin/ruby -I. ../../../../ext/jpeg/extconf.rb
checking for jpeglib.h... yes
checking for main() in -ljpeg... yes
creating Makefile
cd -
cd tmp/x86_64-linux/jpeg/1.9.3
make
compiling ../../../../ext/jpeg/jpeg_jpeg.c
../../../../ext/jpeg/jpeg_jpeg.c: In function ‘jpeg_jpeg_exit’:
../../../../ext/jpeg/jpeg_jpeg.c:32:5: warning: format not a string literal and no format arguments [-Wformat-security]
compiling ../../../../ext/jpeg/jpeg.c
compiling ../../../../ext/jpeg/jpeg_error.c
linking shared-object jpeg.so
cd -
install -c tmp/x86_64-linux/jpeg/1.9.3/jpeg.so lib/jpeg.so

環境が汚れなさそうなのは確認できましたね。

warning直さないと・・・

travisに教える

.travis.yml に以下を追加 (もちろん前のは消して)

1
2
before_script:
 - rake compile

travisのログ確認

39$ rake compile
40mkdir -p tmp/i686-linux/jpeg/1.8.7
(中略)
54install -c tmp/i686-linux/jpeg/1.8.7/jpeg.so lib/jpeg.so

環境も汚してないしテストも通りました! やったね!

参考