seri::diary

日常

RubyでMapReduceを実装している

去年の12月ぐらいからRubyMapReduce*1を実装している。
一応、ちゃんと複数のマシンで分散処理ができるところまで実装できたので、今の進捗をまとめておく。

github.com

最初は分散処理で動作するものではなく、1台のマシンでマルチスレッドで動作する疑似分散処理の実装を作ってMapReduceアルゴリズムの理解を深めるのが目的だったが、せっかくなのでちゃんと複数台のマシンで動作するものを作ることにしてみた。

概要

システム全体のアーキテクチャは以下の通り。

f:id:serihiro:20180212175809p:plain

とは言っても、実用が目的ではないので以下の制約がある(Combinerはそのうち実装するかも)

  1. Maptaskは1台のnodeで動作し、Reduectaskは空いているJobWorkerの分だけshuffleして並列に動作する
  2. Combiner未対応なのでMaptaskのoutputをそのままshuffleする
  3. shuffleはkeyのhash値を空きJobWorkerの数で割った余りを使う単純なhashパーティションのみ
  4. MapReduceのinput/output/処理途中の中間データは全てS3に保管し、データのローカリティは一切考慮しない
  5. 途中で処理が失敗してもリカバリする手段はない

ローカルで動作させている様子をキャプチャしてみたが、なんとなくこれで伝わるだろうか。
S3の代わりにminio*2をローカルで実行し、JobTrackerが1host, JobWorkerが3host起動しており、maptaskが1hostで実行され、その後, reduectaskが2hostで実行されている様子である。

www.youtube.com

使い方

クラスタの起動の仕方はREADMEに書いたのでこちらを参考していただきたい。 Dockerイメージも用意したので、めんどくさい人は docker-compose up 一発でクラスタを起動できる。

ジョブは、MapタスクとReduceタスクをそれぞれ別クラスで用意すればよい。 なお、自分で用意するのがめんどくさい人向けにCLIにサンプルが入っており、手順だけまとめると、docker-compose upで起動した場合は以下のようにすればWordCountが動かせる。

$ docker-compose exec job_tracker bundle exec simple_map_reduce generate_lorem_text_data --upload=true
$ docker-compose exec job_tracker bundle exec simple_map_reduce execute_word_count

ちなみにここで実行しているWordCountのJobは以下のようなコードである。*3

class WordCount
  def map(input_data, output_io)
    input_data.split(' ').each do |raw_word|
      word = raw_word.strip
      next if word.empty?
      word.delete!('_=,.[]()#\'"-=~|&%')
      word.downcase!

      output_io.puts({ key: word, value: 1 }.to_json)
    end
  end
end
require 'json'
class WordCount
  def reduce(input_io, output_io)
    output = Hash.new(0)
    count = 0
    input_io.each_line(chomp: true, rs: "\n") do |line|
      input = JSON.parse(line, symbolize_names: true)
      output[input[:key]] += input[:value]
      count += 1
      if count % 100 == 0
        puts "current count: #{count}"
      end
    end

    output.each do |key, value|
      output_io.puts(JSON.generate(Hash[key, value]))
    end
  end
end

これをどうやってJobTrackerに渡して実行しているかというと、ソースコードをStringとしてJobTrackerにPOSTし、temporaryなクラスを生成した上でそのクラス内クラスとしてclass_evalして定義する、という力技により実現されている。*4
Hadoopだとjobをjarとして生成してjarをそのままNameNodeに渡すようになっているが、スクリプト言語である以上こうするしか思いつかなかった。。

使ったライブラリなど

sinatra

実はsinatra*5をちゃんと使ったことがなかったので、勉強を兼ねて使ってみた。
sinatra自体は歴史あるプロダクトなので今さら特に語ることもないのだが、1クラス1アプリの単位で実装できるのが結構都合がよかった。今回作った実装ではデータの永続化を一切しておらず、sinatraアプリとして実装したクラスのクラスインスタンス変数にすべて突っ込む力技*6を採用しているのだが、データストアとしての役割も兼任させる上では1クラス1アプリという単位は管理上都合がよかった。

また、rubyのクラスとしてweb appをそのまま実装して起動できるので、rubyからふつうに起動できるのも便利だと思った。railsだと bundle exec rails s みたいな感じで、普通はシェルスクリプトなどから実行するしか手段がないのだが、sinatrarubyスクリプトとして実行する以外の起動手段を持っている。

例えば今回作ったMapReduce実装には管理用のCLIを添付したのだが、このCLIでJobTrackerを起動する部分は以下のように実装している。*7

SimpleMapReduce::Server::JobWorker.run!(port: config.server_port, bind: '0.0.0.0') do
  SimpleMapReduce::Server::JobWorker.setup_worker
end

run! で起動できるのは割と周知された方法だと思うが、さらにblockを渡すことで起動前に処理を独自のcallbackを追加できる。これはドキュメントを調べても見当たらず、結局sinatraのソースを眺めていて見つけた。*8

また、同様にソースを見ていて発見した例として、sinatraアプリを終了するときに実行されるSinatra::Base.quit! *9をOverrideすることで、sinatraアプリを終了する時にもcallbackを挟むことができるので、WebRickを終了させる前にWorkerを終了させるのに利用している。*10

# @override
def quit!
  @keep_polling_workers = false
  @polling_workers_thread.kill
  job_manager.shutdown_workers!
  super
end

MessagePack

Web APIへデータを渡すときのserializeはいつものようにJSONでいいかなとも思ったが、せっかくなのでMessagePack*11を使ってみた。
独自にTypeを定義すれば自前のクラスもserialize/deserializeできるようだが*12、今回はとりあえずHashとして各種プロパティの値をdumpしたデータをserializeしているだけである。いずれ直接JobやTaskをserialize/deserializeできるようにしてみるのも面白いかもしれない。

Worker Threadの管理

Threadで非同期実行するWorkerを管理をするために別のgemを実装した。

github.com

中身を見てもらえば分かるがSidekiq*13っぽいI/Fで指定したクラスをjobとしてキューイングして、Thread poolに入ってるThread群で並列に実行できる、という程度のものである。JavaのConcurrency UtilにあるExecutorServiceみたいなものの超簡易版ぐらいに思ってもらえれば幸いである。

Sidekiq等のJobQueueと異なる点として、外部のデータストレージをキューストアとして使っていない。漢らしいオンメモリキューストアである。もちろんjobの優先度も管理していない。 jobをEnqueueする部分はこんな感じである。*14

module Rasteira
  module EmbedWorker
    # Manager class that manages the thread pool and executes jobs.
    class Manager
      attr_reader :job_pool

      def initialize
        @job_pool = []
        @thread_pool = []
        @mutex = Mutex.new
      end

      # 省略

      def enqueue_job!(worker_name, options = {})
        @mutex.synchronize do
          @job_pool << ::Rasteira::Core::Job.new(worker_name, options)
        end
      end

データストアが絡まないとここまで実装をシンプルにできるんだなぁと感心した。逆に、普段どれだけ外部システムとの連携に神経をすり減らしているかが良く分かる。

この漢らしい実装のおかげで、JobTrackerやJobWorkerのsinatraアプリとメモリ空間を共有することができるため、jobやworkerなどのオブジェクトの参照をそのまま渡すことができる。
なので、workerからjobやworkerなどのオブジェクトを直接変更することができる。セキュリティもへったくれもないのだが、実用を考えていないのでセキュリティには一旦目をつむりたい。*15

感想

MapReduceそのものの実装は簡単だったのが、その周辺のworker, job, taskを分散環境で管理するのが難しく、分散処理に関する実装に殆どの工数が取られてしまった。
分散処理で動作するプロダクトを作る際に本質的でない実装にものすごく工数がかかるのはGoogleの論文にも書いてあった通りで、まさに身をもって再試をした気分である。

しかし、お陰で何となくHadoopなどの分散処理フレームワークが裏で何をやってくれていて、その恩恵によってどれだけ本質的な処理にだけ集中できるようになったのかが少しは理解できたと思う。 今回作った実装についてはまだいろいろと実験しがいがありそうなので、今後も時間を見てアップデートしていく予定である。 今は分散処理や大規模データ処理の高速化のアルゴリズムについて興味があるので、今後も実装を通じて学んでいきたい所存。

今更ながらTurbolinksを初めて仕事で使ってみたので色々調べてみた

f:id:serihiro:20140802065933j:plain

※このエントリーで使用している検証環境の各種バージョンは下記の通りです。

  • Railsのバージョンは4.1.4
  • Rubyのバージョンは2.1.2p95
  • Chromeのバージョンは36.0.1985.125 m

※このエントリーの最終更新日は2014.8.11です

2013年辺りのRails4について書かれたブログを読むとTurbolinksに関するエントリが結構多いんですね。

ざっとググって1ページ目に来るのがこれらのエントリー。

そして同じぐらい目にするのが「Turbolinksをオフにする方法」

Railsを使ってなかった頃からtwitterとかでちょいちょい流れてくるTurbolinksの記事を流し読み程度に読んでいたのですが、色んな人が解説エントリを書くほど注目されている一方で、何故か無効にする方法がやたら充実しています。
使っていいのダメなの??ということが外から判断できないということで、どんな恐ろしい機能なのかとjavaを書きながら横目でチラチラを見守っていたのですが、遂に今の職場で使う機会に巡りあいました。これも何かの運命でしょうか。

で、良い機会ですのでTurbolinksについてじっくり調べてみました。 さらに身を持ってその仕様を体験するためにちょっとした実験をやってみたのでその結果をご紹介します。

なお、本エントリーはRailsでアプリを作る人がturbolinksを使う時に気をつけること、というレベルでまとめたので、Turbolinksの実装の詳細についてはあまり触れていませんが、冒頭で紹介したリンク先の内容に詳しく記述されていますのでそちらを読むとさらに理解が深まると思います。

Turbolinksとは

Githubの本家リポジトリrails/turbolinks · GitHubでは以下のように説明されています。

  • Turbolinksはあなたのアプリケーションを早くするよ!
  • ブラウザにjs,cssをリコンパイルさせずに現在のページをActiveに保ってbodyとtitleとheadだけを書き変えるよ!
  • pjaxと似てるけど、Turbolinksは何も考えずにbodyをガバっとreplaceしてくれるのでサーバ側で特別なことをせずにpjaxを使うのとほぼ同じような恩恵を得られるよ!
  • もちろん(現在のページをActiveに保つということは)ブラウザ上でjsの長時間のプロセスを持続することになるので、処理の肥大化やメモリリークに気をつける必要があるけど、あなたがファンキーなことをしなければ多分大丈夫だよ!

要するにページ遷移(GETのみ)を通常のページ移動ではなくajaxで取得したhtmlでbodyを入れ変えるだけの処理に差し替えることで高速化するgemです。 似たようなことをしてくれるライブラリとしてpjaxというjqueryライブラリがあります。

Turbolinksにおけるjsの取り扱い

しかし手放しで何も考えずに入れとけばちゃんと動くというものでもないようです。

  • 最初にロードしたページがActive状態であり続ける
  • jsのプロセスが持続する

この事実を考慮すると今まで書いていたjsがそのまま動くのかちょっと不安になります。

例えばこんなコードはTurbolinksを使ってるページでもページ遷移毎に実行されるのでしょうか?

<script>
window.onload = function(){
  console.log('nya-n');
});
</script>

window.onloadの実行タイミングはページ全体がロードされた後のはず。 ってことは、最初の1ページ目の表示では問題なくwindow.onloadが発火しそうですが、Turbolinksが有効になっているページでページ遷移した時は動くのでしょうか? ついでに頻繁に使う$(document).ready

迷ったら実験しましょう。

実験 各イベントはいつ発火するのか

rails4.1.4で簡単なサンプルを作りました。
サンプルコードのリポジトリこちらです。

$ rake routes
Prefix Verb URI Pattern      Controller#Action
  root GET  /                index#index
 other GET  /other(.:format) index#other

/views/index/index.html.erb

/views/index/other.html.erb

/assets/javascripts/index.js.coffee

rails sして実際にアクセスしてみます。

実験1  index/indexのURLを指定してアクセス

f:id:serihiro:20140810144656p:plain

  1. ベタ書きしたjsの即時関数
  2. assets配下のCoffeeScriptに書いた$(document).ready
  3. ベタ書きした$(document).ready
  4. page:change
  5. page:update
  6. assets配下のCofeeScriptに書いた$(window).load
  7. ベタ書きした$(window).load

実験2 リンクをクリックしてindex/otherへページ遷移

f:id:serihiro:20140810145149p:plain

  1. page:before-change
  2. page:fetch
  3. page:receive
  4. ベタ書きしたjsの即時関数
  5. ベタ書きした$(document).ready
  6. page:change
  7. page:update
  8. page:load

実験3 リンクをクリックしてindex/indexへページ遷移

f:id:serihiro:20140810145730p:plain

  1. page:before-change
  2. page:fetch
  3. page:receive
  4. ベタ書きしたjsの即時関数
  5. ベタ書きした$(document).ready
  6. page:change
  7. page:update
  8. page:load

この結果から以下のことが言えます。

  • $(window).loadはページの初回表示時のみ実行され、リンクをクリックして遷移した場合は実行されない
  • $(document).readyはページの初回表示時のみ実行され、リンクをクリックして遷移した場合は実行されない
  • テンプレートファイルにベタ書きしたjsはページ遷移毎に実行される
  • テンプレートファイルにベタ書きした$(documebt).readyはページ遷移毎に実行される
  • テンプレートファイルにベタ書きした$(window).loadはリンクをクリックして遷移した場合は実行されない

assets配下のCoffeScriptに書いた処理はTurbolinksの管理配下の挙動になり、テンプレートファイルに書いたjsはページ遷移毎にそのまま実行されるようです。
こちらの記事にも書いてありますが、ページ遷移の度にロードされたhtml内のbodyタグ内のjsタグを取得して、新規のjsタグを組み立て再度差し込んで実行させているようです。

処理的には、turbolinks本体のCoffeeScriptを読み込んだ時にAタグをクリックした際にxhrオブジェクトを作ってxhrオブジェクトからリクエストを飛ばす処理をbindしているのですが、ページ遷移時の詳細な挙動はQiitaのこちらの記事に詳しく説明されています。 CoffeeScript側のソースだけなら436行しかないのですぐ読めますので気になる方は一度全部読んでみると良いと思います。実装自体は割と素直に書いてありますので読みやすいです。

Turbolinks導入のメリット

最初に書いたようにTurbolinksを導入することの利点はページロードの高速化です。html全体を再ロードしないため、ブラウザ上での体感速度向上が期待できます。

Turbolinks導入のデメリット

これまで書いてきたjsが動かなくなる可能性が高い

通常のwebページとjsの挙動が大きく変わるためこれまで作ってきたjsが上手く動作しない可能性が高くなります。
これまでRails3で作ってきたrailsアプリケーションをrails4にアップグレードした途端に正しく動かなくなるといったことが簡単に発生します。

ページ遷移してもmetaタグが更新されない

ページ遷移してもCSRF-Token以外のmetaタグが更新されないという挙動になっています。

そのため、普通ページ毎に内容が異なるogタグ等もページ遷移しても更新されません。 しかし、実際にfacebookのいいね!ボタンをクリックしたりした場合においては、フルロードしたコンテンツを各SNSが取得してくれるはずなので問題無いのでは?というようなコメントがついています。DHHも「それが必要なユースケースは聞いたことがない(I haven't heard a compelling use case yet)」とコメントしています。

既存のjsが動かなくなるケース

既存のjsをそのまま使おうとした時にどんな問題が起きるか具体例を上げて説明します。

$(document).readyにイベントをbindする処理を書いてたら最初にアクセスしたページではbindされるが遷移したらbindされない

$(document).readyはリンククリックでのページ遷移では発火しないので、同じjsファイルを読み込んでいるのに「外部リンクから最初にやってきた時しか正しく動かない」ということが起きます。

$(document)に同じイベントが多重bindされて正しい挙動にならない

全ページで読み込むjsで(例えば共有しているテンプレートファイルにベタ書き)即時関数で以下のbind処理を記述したとします。

<script>
$(function(){
  $(document).on('click', '#button', function(){
    console.log('ニャーン');
  })();
});
</script>

で、何度もページ遷移してから#bindをクリックするとページ遷移した分だけconsoleに「ニャーン」と表示されます。猫好きの私歓喜!とか言ってる場合じゃありません。

これが発生する原因は、最初に書いたとおりjsのプロセスがずっと継続するため、$(document)のオブジェクトはページ遷移してもずっと同一のものが生存し続けています。
そのため、ページ遷移する度にbindするようにすると、同一の$(document)オブジェクトに再度bindしてしまうため、何重にも同じイベントがbindされることになります。 私も最初この挙動にハマって小一時間悩みました…。1回のアクションでajaxリクエストが複数回飛んでるっぽい?などの分かりにくい問題が起こります。

なんでみんなオフにしたがるのか?

既存アプリにTurbolinksを導入する場合、「デメリットの方が多くなるケースが多い」ということに尽きると思います。 具体的には * jsをTurbolinksの挙動に合わせて修正するコストが高い * Turbolinksの挙動を正しく理解していないと簡単にトラブルになる * フロントエンドを担当するエンジニアへの負担が大きくなる といった所が問題になりそうです。想像の範囲ですが。

それでもTurbolinksを有効にする場合に気をつけること

urbolinksを使用する際のjs実装について気をつけることについて説明します。

ページ遷移の度に実行したいjsの実装

lightboxやmasonryなどのデザイン系のjQueryプラグインを適用するケースによくある「ページ遷移の度に実行したい」場合。 多重実行されるとやっぱり挙動がおかしくなるので、「複数回実行はさせたくないがページ遷移毎に1回だけ実行したい」という要件になります。

これを実現するにはいくつか方法があります。

1. テンプレートファイルにベタ書きする

毎回実行されることが保証されていますのでテンプレートファイルに記述すれば良さそうです。ただ、jsはassetsで管理してapplication.jsの1ファイルにまとめてロードするというrailsの作法からはちょっと外れる形にはなります(これも厳密に守るのはかなり難しい作法ですが)

2. $(document).readypage:loadの両方で実行されるようにassets管理下のjs,coffeescriptに記述する

$(document).readyはページ初回表示時にのみ実行され、page:loadはページ遷移時にのみ実行されます。ページ初回表示時には実行されません。

なので、同じ処理を両イベントでbindしておけば、常にページ遷移ごとに一回だけ実行されることが保証されます。

以下のように記述します。

$(document).on 'ready page:load', -> 
  console.log 'ready and load'

これでページ遷移毎に実行され、かつ一回だけ実行されます。

ただし、ブラウザバックで戻った時にはpage:loadが実行されないので、ブラウザバックで実行されなくて困る処理はpage:changeにbindする必要があります。 しかし、turbolinks使用時にブラウザの戻るボタンをクリックした場合、turbolinksが内部でキャッシュしているpageCacheからページが復元され(turbolinksはURLの書き換え等にhisotry APIを使っています)、pushStateが使えない場合はリダイレクトされる(location.hrefを書き変えてます)ので大体の場合において問題は起きないと思います。

イベントのbind

難しいのはこっちで、いくつか方法が考えられますがどの方法を取るにしても開発チーム内で話し合って決めておく必要があります。 主な関心事としては「多重bind」をいかに防止するかということになりますが、逆に多重bindされても問題がないようにイベントのハンドラを実装しておくというの手です。

まず前提条件としてテンプレートファイルにベタ書きせず、assetで管理されているjs、CoffeeScriptのファイルに記述することが条件になります。
テンプレートファイルにイベントをbindする処理を普通に書くとページ遷移毎にイベントがbindされるので$(document)の場合は上手くワークしません。 テンプレートファイルにjsを書かなければいけないケースはControllerから値を渡すようなケースに限定して、使わないように開発チーム内で取り決めをしておく必要があります。

1.$(document)にbindする場合

ajaxで後からロードするhtmlに対してイベントを定義しておく場合などによく用いられるケースです。

assetでapplication.js1つにjsがまとめられる場合は、$(document).ready内でbindするように記述します。

■/assets/path/index.js.coffee

$(document).ready ->
  $(document).on 'click', '#popup', (e)->
    alert('popup') 
  $(document).on 'hover', '.menu', (e)->
    e.currentTarget.addClass('hovered')

上述の通り初回表示時にのみ実行されるため、初回表示時に$(document)にbindする全てのイベントがbindされその後ページ遷移しても実行されません。
これにより確実に一回だけイベントがbindされます。ページ遷移毎に$(document) にbindするように実装すると上述した通り同じイベントが同じDOMに何重にもbindされて不具合を起こすケースがあります。

イベントのbind自体は$(document)に直接bindするのが一番早いようですし、パフォーマンス的にも問題は起こりにくい方法だと考えられますので、特別な理由が無い限り、イベントのbindは$(document).on({event}, {selector}, {callback})の形式に統一してしまうのが良いのではないかと思います。
参考:高速で安全なjQueryを書くために今できること | Dress Cording

ページごとにjsを読み込む場合は、そもそもturbolinksのページ遷移にならず通常の遷移になるので気にする必要はないですが、turbolinksの恩恵を受けられなくなります。

2.指定のセレクタにbindする場合

$(document).'ready page:load'でbindするようにすればページ遷移毎に実行されますので、これで対応出来ると思います。

こんな面倒なことしてられるか!俺はRails4でも今までどおりに開発させてもらう!という場合

特定のリンク時のみturbolinksで遷移せず通常のページ遷移をするようにする場合

aタグにdata-no-turbolinkという属性を付けます。 例:

<a href="/user" data-no-turbolink>User List</a>

全ページでリンククリック時のページ遷移にturbolinksを使わない場合

gemファイルとturbolinksを読み込んでる箇所をjs,htmlから削除します。turbolinksのソース読み込んだ時にAタグにbindingされるんでそりゃそうですが。
詳しい手順はこちらを参照。

まとめ

以上、簡単ですがturbolinksの挙動と実際に使う場合の注意点について述べました。
文中でも書いたとおり、これまでの色々なweb上でのjsの常識が通じなくなりますので、既存のjsを流用する場合はかなりの手間が必要になります。
またAngular.jsなどのフロントエンドフレームワークを使う場合においても対応が必要なようです。

そのためjsを多用したSPAを作る場合や、開発メンバーが不慣れな場合はturbolinks自体使わないという選択肢もアリだと思います。正直私も最近初めて使ったのですが最初は色々なものが動いたり動かなかったりでなんじゃこりゃと思いました。

ただ、Rails4から標準で有効になっている以上、Railsの作法の一つとして実装されているものだと考えています。
今後のバージョンアップでどうなるか分かりませんが、今後Railsで新規アプリを作っていくのであれば、うまい付き合い方を知っておいた方が結果的にRails Wayの恩恵をより受けられるようになっていくのではないかと勝手に考え、今回の機会にまとめてみました。

RubyとRailsのバージョンをrvmで管理する

色々あってRailsの勉強してる。仕事以外だけど。
まだ具体的な開発には入っていないが、久しぶりのrails環境構築でハマりまくって大変なことになったので
必要最低限のところだけメモ。

対象バージョン

今回rvmでインストールして管理するのは下記のバージョン

ホントはRails4 beta0を使いたかったのだがいろんな事情(herokuが対応してないとか)で断念

rvmでruby1.9.3をインストールする

#まずrvmを更新。これやらないとハマる確率が2割ぐらい上がる
$ rvm get stable
$ rvm -v
rvm 1.19.6 (stable) by Wayne E. Seguin <wayneeseguin@gmail.com>, Michal Papis <mpapis@gmail.com> [https://rvm.io/]

#必要なパッケージ類をインストールする。これをしないと、後続作業であれがない、これがないと怒られまくって大変なことになる
$ rvm pkg install openssl
$ rvm pkg install readline
$ rvm pkg install zlib
$ rvm install 1.9.3


上記で失敗する場合は~/.rvm/usrを消してから再度pkgをインストールしてみると上手くいくことがある。
自分の場合何度やってもrubyのコンパイル時にopensslが読み込まれず涙目になっていのだが、ダメもとで~/.rvm/usrを消したら上手くいった。

なお、opensslが組み込まれない場合、rubyのインストール時に下記のようにコンパイルオプション指定で要なパッケージのディレクトリを指定する方法が色々なサイトで紹介されていた。
しかし上記の手順でrvmで各種pkgがインストールされている場合は不要のようだった。
rvm管理外のopensslを使いたい時とかに使うオプションだと思われる。

$ rvm install ruby-head -C "--enable-shared=true,--with-opt-dir=/opt/local"


ちなみに、ruby2.0.0が使いたくなってruby2.0.0をrvmでインストールしたときにも似たような事態(opensslがrequireできない病)にハマって、
半日ぐらい四苦八苦した結果、以下のコマンドでうまくインストールできたログがkobitoに残ってたのでさらしておく

$ CC=clang rvm install 2.0.0 --skip-openssl --with-opt-dir="brew --prefix openssl"

因に各コマンドの意味はよくわからない。あとで調べてみる。


rvmでRailsをインストールする

rvmはrubyrailsのバージョンの組み合わせを"gemset"という単位で管理することができる。
例えば
ruby1.8.7とrails3.1のgemset
ruby1.9.2とrails3.2のgemset
という具合に管理することができる。異なるrailsバージョンのアプリを同じPC内で開発しなくてはいけない場合等に効果を発揮しそうである。
そうえいばhome brewでもphpを複数バージョン管理できるが、phpの複数バージョン同居はそこまで苦痛ではないのでいまいち恩恵を感じなかった。
しかし今回railsの環境構築で相当痛い目を見た私としては、rvmのgemset管理機能が神に見える。

今回は上記でインストールしたruby1.9.3とrails3.2.13を一つのgemsetとして管理する。

$ rvm gemset create rails32

#作ったgemset を使用する
$ rvm 1.9.3@rails32
#確認する
$ ruby -v
rruby 1.9.3p392 (2013-02-22 revision 39386) [x86_64-darwin11.4.2]

$ gem install rails
$ rails -v
Rails 3.2.13

もし新しいバージョンのrailsが出て(というかもう4.0.0betaがあるけど)試したくなったら

$rvm gemset create rails40
$rvm 1.9.3@rails40
$gem install

とやれば試すことが出来る。

また、同一バージョンのrubyでgemsetだけ複数作成して一発で切り替えることもできる。
詳細はこちらのエントリーが非常に非常に詳しいので熟読させていただいた。
rvm 入門 … 複数バージョンの Ruby と Rails を混在させる


今回の作業で参考にした他のリンク
Macで環境を整えるための環境-homebrewとrvmとrubygems-
RVMのRuby環境にreadline,openssl,zlibなどのライブラリを追加インストールする
rvm.ioのopensslのquick fix



色々あったけどrvmはやっぱり便利。
次回からはRubyRailsや、はたまた侍ズムの山本さんにおすすめされているRubymineのエントリーがかければ良いと思う。


あと全然関係ないけどドミノ・ピザでピザ食った

Windows環境にRuby on Rails3.2.6を入れる時の注意点

Ruby on Rails3.2.6をWindows環境にgemでインストールする時に若干ハマったのでメモ


概要:

rails installをして
指定のフォルダでrails new hogehogeして
rails serverしようとするとgemさんに
「アレがないコレがない」と言われてWEBrickが起動しない。

bundle installしようとするとbundle installがそもそも失敗する。
それを解消する為のメモ。

1.gem install railsで以下のメッセージが出る

Please update your PATH to include build tools or download the DevKit
from 'http://rubyinstaller.org/downloads’ and follow the instructions
at 'http://github.com/oneclick/rubyinstaller/wiki/Development-Kit’

対策:

1.http://rubyinstaller.org/downloads/からDEVELOPMENT KITをDL
2.マルチバイト文字を含まないフォルダ名のフォルダに解凍。
Rubyを実行する際に読みに行くフォルダになるため、
めんどくさがって「新しいフォルダ」とかに解凍してはいけない。
絶対絶対いけない。
*1

3.解凍したフォルダにcd
4.以下のコマンドを実行

ruby dk.rb init
ruby dk.rb review
ruby dk.rb install

2.bundle installでこける

Gem::RemoteFetcher::FetchError: SSL_connect returned=1 errno=0 state=SSLv3 read
server certificate B: certificate verify failed (https://rubygems.org/gems/coff
e-rails-3.2.2.gem)
An error occured while installing coffee-rails (3.2.2), and Bundler cannot cont
nue.
Make sure that `gem install coffee-rails -v '3.2.2'` succeeds before bundling.

対策:

1.以下のコマンドを実行。DL先がhttpsだとDLに失敗するバグが修正される。

gem update --system


ここまでやってようやくrails serverが動いた。やれやれ

*1:~\lib\ruby\site_ruby\1.9.1\rubygems\defaults\operating_system.rbの
環境変数を設定する箇所に日本語が入っていると文字コード不一致でsyntaxエラーになって
rubyが実行できなくなる。おお怖い怖い