さくらのレンタルサーバでRuby on Rails 4.2.3を動かす

久しぶりのIT記事、しかもRails。
でも、仕事でやってるのとは全然関係なし。というか、こんなのビジネスにならないよ。

前提。
・さくらのレンタルサーバ スタンダード
・root権限なし、Apache設定変更不可
・WEBrickのdevelopmentモードでWebDAVポートをすり抜けるとかしないで、Apache・productionモード・ポート80というごく一般的なRails アプリ構成


ついでにRubyを2.1.1→2.1.6へ。
ソースビルドなので上書きにインストール。
Rubyのconfigureでprefix付けるの忘れずに。
$ ./configure --prefix=/home/****/local
$ make
$ make install

最小限のGemfileで「bundle install --path vendor/bundle」する。

source 'https://rubygems.org'

ENV['NOKOGIRI_USE_SYSTEM_LIBRARIES'] = 'YES'
gem 'rails', '~>4.2.1'

NOKOGIRI~は入れないとエラーになるぽい。
http://qiita.com/suu_g/items/fcf549e16c797a9d7dc0


$ bundle list
Gems included by the bundle:
* actionmailer (4.2.3)
* actionpack (4.2.3)
* actionview (4.2.3)
* activejob (4.2.3)
* activemodel (4.2.3)
* activerecord (4.2.3)
* activesupport (4.2.3)
* arel (6.0.2)
* builder (3.2.2)
* bundler (1.10.3)
* erubis (2.7.0)
* globalid (0.3.5)
* i18n (0.7.0)
* json (1.8.3)
* loofah (2.0.2)
* mail (2.6.3)
* mime-types (2.6.1)
* mini_portile (0.6.2)
* minitest (5.7.0)
* nokogiri (1.6.6.2)
* rack (1.6.4)
* rack-test (0.6.3)
* rails (4.2.3)
* rails-deprecated_sanitizer (1.0.3)
* rails-dom-testing (1.0.6)
* rails-html-sanitizer (1.0.2)
* railties (4.2.3)
* rake (10.4.2)
* sprockets (3.2.0)
* sprockets-rails (2.3.2)
* thor (0.19.1)
* thread_safe (0.3.5)
* tzinfo (1.2.2)

「bundle exec rails new . --skip-bundle -d mysql」でbundle飛ばしてRails app作成

MySQLをサーバコントロールパネルから作成。
DB名・ユーザ名が自由にならないのでちょっともやもや。仕方ない。
collationだけutf8_general_ciにして後は4.2系デフォルトのまま。
今回はdevelopment、productionを同じdbにした。

Gemfileを確認してbundle install。

source 'https://rubygems.org'

gem 'rails', '4.2.3'
gem 'mysql2'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'

gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc

gem 'bcrypt', '~> 3.1.7'

group :development, :test do
gem 'byebug'
gem 'web-console', '~> 2.0'
gem 'spring'
end

さくら鯖はtherubyracer(libv8)がダメなのでnode.jsを使うけど、前に参考にしたサイト(http://blog.tmtr.jp/2013/04/nodejs.html)がリニューアル中なのか閲覧できない。
代わりに別のサイト→http://uguisu.skr.jp/Windows/node.html
私は前にRails 3.2で遊んだときに入れたnode.jsがあったのでそのまま利用。

$ bundle list
Gems included by the bundle:
* actionmailer (4.2.3)
* actionpack (4.2.3)
* actionview (4.2.3)
* activejob (4.2.3)
* activemodel (4.2.3)
* activerecord (4.2.3)
* activesupport (4.2.3)
* arel (6.0.2)
* bcrypt (3.1.10)
* binding_of_caller (0.7.2)
* builder (3.2.2)
* bundler (1.10.3)
* byebug (5.0.0)
* coffee-rails (4.1.0)
* coffee-script (2.4.1)
* coffee-script-source (1.9.1.1)
* columnize (0.9.0)
* debug_inspector (0.0.2)
* erubis (2.7.0)
* execjs (2.5.2)
* globalid (0.3.5)
* i18n (0.7.0)
* jbuilder (2.3.1)
* jquery-rails (4.0.4)
* json (1.8.3)
* loofah (2.0.2)
* mail (2.6.3)
* mime-types (2.6.1)
* mini_portile (0.6.2)
* minitest (5.7.0)
* multi_json (1.11.2)
* mysql2 (0.3.18)
* nokogiri (1.6.6.2)
* rack (1.6.4)
* rack-test (0.6.3)
* rails (4.2.3)
* rails-deprecated_sanitizer (1.0.3)
* rails-dom-testing (1.0.6)
* rails-html-sanitizer (1.0.2)
* railties (4.2.3)
* rake (10.4.2)
* rdoc (4.2.0)
* sass (3.4.16)
* sass-rails (5.0.3)
* sdoc (0.4.1)
* spring (1.3.6)
* sprockets (3.2.0)
* sprockets-rails (2.3.2)
* thor (0.19.1)
* thread_safe (0.3.5)
* tilt (1.4.1)
* turbolinks (2.5.3)
* tzinfo (1.2.2)
* uglifier (2.7.1)
* web-console (2.2.1)

とりあえずテストなのでスキャフォで。

bundle exec rails g scaffold User number:integer name:string email:string
bundle exec rake db:migrate
bundle exec rails s

動作だけ確認。


ここからが本番。
参考:http://takuya-1st.hatenablog.jp/entry/20120108/1326043011

でも、参考サイトと違うのは「Apacheの設定は弄れない」「bundlerを使う」という点。
さらに、Apache+Passengerの時のように「Web公開ディレクトリ外のアプリディレクトリ/publicにシンボリックリンク」もしたい。
(要するに、/home/****/www/rails/testappが、/home/***/rails/testapp/publicへのシンボリックリンクになる)
なので色々試行錯誤。

いろいろいあって、最終的に

index.cgi(参考記事ではdispatcher.cgi)

#!/home/****/local/bin/ruby
begin
ENV["EXECJS_RUNTIME"] = "Node"
ENV["RAILS_ENV"] = "production"
ENV["SECRET_KEY_BASE"] = "..." # bundle exec rake secretの結果
ENV["RAILS_RELATIVE_URL_ROOT"] = "/rails/testapp"
ENV['RAILS_SERVE_STATIC_FILES'] = "true" # 強引にCGIで動かしているため

require 'rubygems'
require 'bundler/setup'
require 'cgi'
require 'rack'
require '/home/****/rails/testapp/config/environment'

class Rack::PathInfoRewriter
def initialize(app)
@app = app
end

def call(env)
env['SCRIPT_NAME'] = ENV["RAILS_RELATIVE_URL_ROOT"]
parts = env['REQUEST_URI'].split('?')
env['PATH_INFO'] = parts[0].sub(%r!\A#{env['SCRIPT_NAME']}!,"")
env['QUERY_STRING'] = parts[1].to_s
@app.call(env)
end
end

Rack::Handler::CGI.run Rack::PathInfoRewriter.new(Testapp::Application)
rescue Exception => e
print "Content-Type: text/plain\r\n\r\n"
print "Got an error."
end

.htaccess
RewriteEngine ON
RewriteBase /rails/testapp/
RewriteRule ^.+$ index.cgi [QSA,L]

※実際はこれにBasic認証かけたけど、本題から外れるので省略

あと、node.jsのパスが通らないのでvendor/bundle/ruby/2.1.0/gems/execjs-2.5.2/lib/execjs/runtimes.rbを強引に書き換え
(バージョンは適当に読み替え)

Node = ExternalRuntime.new(
name: "Node.js (V8)",
command: ["nodejs", "node", "ローカルのnodeパスをここに追加"],
runner_path: ExecJS.root + "/support/node_runner.js",
encoding: 'UTF-8'
)


結論から言うと…動いた。完璧なまでに動いた。
MySQLを使ってデータ読み書きできるし、ルーティングもアセットパイプラインも完璧。当然production環境で。
ただ…お、遅い!!1リクエストあたり5秒ぐらいかかる。
何故遅いかというと、リクエスト毎にRailsアプリを新規で立ち上げて、終わったらそのまま捨ててるから。
Passenger使った場合でも「touch tmp/restart.txt」した後の初回アクセスは結構もっさり。
それが毎回なんだから遅くて当然。

とりあえず、動くには動いたけど、さすがに公開サービスにするのは無理すぎるね、これ。
おとなしくHerokuのHobby dynoでも借りた方が良さそう。

あるいは、RailsじゃなくてSinatraを動かせばまだましかな…。
http://parrot.hatenadiary.jp/entry/20120118/1326868877