CodeLog

開発メモ

うらしまたろう状態

5ヶ月も更新できていませんでした。危うくはてブロのログインパスワードすら思い出せないところでした。
続きをやろうと思ったのですが、色々忘れてしまっていて開発環境にまで辿り着くのがやっとです。
また、間が空いてしまうこともあるかもしれないので、嵌ったところを記録しておきます。

仮想マシンが起動しない

  • Hyper-VUbuntu環境を構築してやっていたよな…と思ってHyper-Vマネージャーを起動してみたら、仮想マシンが停止していた
  • PC起動したら勝手に起動しておくようにしておいたような気がしたけどなぁ
  • まぁいいかと起動ボタンをポチッとしたら警告音とともに「ハイパーバイザが実行されていないため仮想マシンが起動できません」とのこと
  • 設定系は一切触っていない(ブラウジング以外の用途でマシンを使っていない)ところには自信があったのだが、こういう七不思議的なことって時々あるもので、BIOSの設定が変わっているではないか!
  • で、「Intel Virtualization Technology」をEnabledにしても、マシンを再起動してもすぐには戻らず、何でだぁ…としばらくもがいていたところ、復活した模様
  • そう言えば前にも時間が解決してくれたことがあったような気がする
  • 教訓:気長に待つこと

PuTTYからの接続方法を忘れた

  • PuTTYから仮想マシンに接続してあれこれしていたので、当然のように保存してある接続情報から接続しようとしたのだが「ホストが見つからない」だと
  • ホストOSからホスト名でpingを打っても応答がないが、IPアドレスだと応答あり
  • ははーん、さてはDHCPでhostsに書いたIPから変わってしまったんだなと自信たっぷりにファイルを見たけど書いてなかった
  • IPアドレスで接続できるからイイやということで謎のまま

こんな調子で、RoRの話に戻れるのでしょうか。

CapsLockをCtrlにする

どのマシンでも同じ操作ができるようにするために、こういったことはあまりやらないようにしてきたのですが、emacsを少し使ってみたいという気持ちもあって、emacs使いの人たちがよく言っているCapsLockをCtrlにするのをやってみようかと思いました。
以下はレジストリを変更する手順なので、自己責任でやりましょう。

変更手順

  1. regeditを起動
  2. 「HKEY_LOCAL_MACHINE」→「SYSTEM」→「CurrentControlSet」→「Control」→「Keyboard Layout」を選択
  3. メニューから「編集」→「新規」→「バイナリ値」より「Scancode Map」を作成
    • 値のデータ
0000 00 00 00 00 00 00 00 00
0008 02 00 00 00 1D 00 3A 00
0010 00 00 00 00

再起動後にCapsLockがCtrlになっていることを確認しました。

Ruby on RailsアプリをHerokuへデプロイ

Ruby on Railsチュートリアルに沿って勉強中です。
前回Herokuのユーザアカウントを作成してから少し間が開いてしまいましたが、今日は実際にRailsアプリをデプロイしてみようと思います。

Heroku向けのGemfile作成

  1. Gemfileにproductionをターゲットにして「pg」と「rails_12factor」を追記
    • pgはPostgreSQLですが、productionではHerokuがサポートしているPostgreSQLを使用します
    • rails_12factorについはあまり良くわかっていませんが、Heroku上で動作できるようにRailsの動作を変更するライブラリのようです。ログの保存先が変更されたり、静的アセット(画像、スタイルシート、jsファイル)に微調整が加えられるとのことです
  2. 「bundle install --without production」コマンド実行
    • 「--without production」を指定することで、ローカルに「pg」と「rails_12factor」はインストールされなくなります
    • ここではGemfile.lockを更新してコミットしておくために、bundle installを実行します
  3. gitに変更内容をコミット
$ tail -n 4 Gemfile
group :production do
  gem 'pg', '0.15.1'
  gem 'rails_12factor', '0.0.2'
end
$ bundle install --without production
Fetching gem metadata from https://rubygems.org/..........
Resolving dependencies...
Using rake 10.4.2
...(中略)...
Your bundle is complete!
Gems in the group production were not installed.
Use `bundle show [gemname]` to see where a bundled gem is installed.
$ git commit -a -m "Update Gemfile.lock for Heroku"
[master 09df5a9] Update Gemfile.lock for Heroku
 2 files changed, 13 insertions(+)

Herokuにログイン

  1. https://toolbelt.heroku.com/:topをインストール
  2. 「heroku login」コマンドでHerokuにログイン
  3. 「heroku create」でアプリケーションを作成(アプリのルートディレクトリでコマンドを実行)
  4. Gitを使用してHerokuにリポジトリをプッシュ
    • これだけでデプロイ完了だそうです!
  5. 「heroku open」でブラウザ起動
    • …できるらしいですが、直接ブラウザから「https://radiant-crag-NNNN.herokuapp.com/」を開きました
    • Rails4.0ではエラーページになってしまうらしいですが、ルーティングの追加で解消するそうです
$ wget -qO- https://toolbelt.heroku.com/install-ubuntu.sh | sh
...(中略)...
$ heroku login
Enter your Heroku credentials.
Email: <email address>
Password (typing will be hidden):
Authentication successful.
$ heroku create
Creating radiant-crag-NNNN... done, stack is cedar-14
https://radiant-crag-NNNN.herokuapp.com/ | https://git.heroku.com/radiant-crag-NNNN.git
Git remote heroku added
$ git push heroku master
Counting objects: 67, done.
Compressing objects: 100% (56/56), done.
Writing objects: 100% (67/67), 15.24 KiB | 0 bytes/s, done.
Total 67 (delta 6), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Ruby app detected
remote: -----> Compiling Ruby/Rails
remote: -----> Using Ruby version: ruby-2.0.0
...(中略)...
remote: Verifying deploy... done.
To https://git.heroku.com/radiant-crag-NNNN.git
 * [new branch]      master -> master

ここまででチュートリアルの第1章が終わりました。
新しいことを始めると、ここまでのステップが大変ですが、このチュートリアルはかなり親切に記述されていて助かりました。関連して身に付けるべき(身に付けておきたい)知識についても、ある程度調べながら進めてくることができたので良かったです。とは言え、まだ理解できていない部分も多いので、今後の課題の意味も含めて箇条書きにしておこうと思います。

*1:Subversionとの違いは上手いことまとめられていません。悪しからず。

Herokuとは?

Ruby on Railsチュートリアルに沿って勉強中です。
第1章も終わろうかというあたりで「1.4 デプロイする」という項に入りました。

かつてはRailsアプリのデプロイは大変な作業でしたが、ここ数年急速に簡単になってきており、さまざまな本番環境を選択できるようになりました。

この時点では、さまざまな本番環境って言われても、チュートリアルでお勉強中の身なのに本番環境って何のことでしょうか?と思っていました。

著者のお気に入りはHerokuで、Railsを含むRuby Webアプリ用のホスティングプラットフォームです。Herokuは、ソースコードのバージョン管理にGitを使用していれば、Railsアプリケーションを簡単に本番環境にデプロイできます

Herokuって何のことでしょう?もしかしてアプリをデプロイして公開させてくれるような環境を無料で借りることができるのでしょうか?

ということで、「PaaSの基礎知識とHerokuで開発を始める準備 (1/3):CodeZine」や「FAQ:Herokuってなに?メリットは? - builder by ZDNet Japan」を読んでみました。

Heroku

  • AWSのIaaS(Infrastructure as a Service)に構築されたPaaS(Platform as a Service)
  • Webアプリケーションの開発から公開まで非常に簡単にできる優れたプラットフォーム
  • リポジトリ管理はGit
  • 2010年にSalesforceが買収

すごいですね。とり急ぎでアカウントを作ってみました。(;´∀`)

初めてのGitHub(続き)

GitHubへプロジェクトを登録したので、今回は少し操作してみます。

ブランチ作成

  1. 「git checkout -b [branch name]」でブランチ作成
  2. 「git branch」でローカルブランチを一覧表示
    • 「*」が表示されるのが現在使用していることを意味しているらしい(ブランチ作成と同時に変更された)
  3. 「git mv」でファイル名変更をして内容変更(README.rdocをREADME.mdに変更)
    • git mvは「mv oldname newname && git add newname && git rm oldname」の省略形らしい
  4. 「git status」で状態を確認
  5. 「git -a -m "change message"」でコミット
    • 現存するすべてのファイル変更を一括でコミットする-aフラグを使うことで「git add .」せずにファイル追加をしている
$ git checkout -b branch_20150322          # ブランチ作成
Switched to a new branch 'branch_20150322'
$ git branch
* branch_20150322
  master
$ git mv README.rdoc README.md             # ファイル名変更
$ vi ./README.md
$ git status                               # 状態を確認
On branch branch_20150322
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        renamed:    README.rdoc -> README.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   README.md

$ git commit -a -m "Improve the README file"
[branch_20150322 74ca3ba] Improve the README file
 1 file changed, 2 insertions(+)
 rename README.rdoc => README.md (86%)

マージ

  1. 「git checkout master」で作業対象をmasterに切り替え
  2. 「git merge [branch name]」でマージ
  3. 「git branch -d [branch name]」使い終わったブランチを削除
$ git checkout master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git merge branch_20150322
Updating 6a12635..74ca3ba
Fast-forward
 README.rdoc => README.md | 2 ++
 1 file changed, 2 insertions(+)
 rename README.rdoc => README.md (86%)
$ git branch -d branch_20150322
Deleted branch branch_20150322 (was 74ca3ba).

ブランチをマージしないで破棄するためには「git branch -D [branch name]」とするようです。「git branch -d [branch name]」では、自動的にマージされてしまうようです。
上記の操作をしてしまったのですが、「git reset --hard ORIG_HEAD」で元に戻りました。CVSSubversionと同じような感覚で作業すると危険かもしれないです…。

push

  1. 「git push」でGitHubに反映
    • GitHubのユーザとパスワードを求められるので入力

これまでVSS, CVS, Subversionを利用してきたのですが、Gitは少し難しく感じました。ブランチも保守フェーズになるまではあまり使ってなかったですし…。
「習うより慣れろ」で困った時に都度調べていくしかない気がしますが、「サルでもわかるGit入門 〜バージョン管理を使いこなそう〜 | どこでもプロジェクト管理バックログ」のような分かりやすいサイトもあるので、ざっと読んでおくのが良いかもしれないです。

初めてのGitHub

.gitignoreの追記

スワップファイルなどの不要ファイルを.gitignoreに追記します。

# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea
.secret

Railsプロジェクトの追加とコミット

Railsプロジェクトをリポジトリに追加します。

$ git config --global alias.co checkout
$ git config --global core.editor vi
$ git init
Initialized empty Git repository in /<rails app path>/.git/
$ git add .
$ git status # ステージングエリアのファイルを確認
$ git commit -m "Initialize repository"
$ git log    # コミットメッセージの履歴を参照

SSH作成とGitHubへの登録

GitHub · Build software better, together.」でアカウントを作成しておきます。
Generating SSH keys - User Documentation」の内容に沿ってSSHキーを作成します。

  1. SSHキーを作成
  2. SSHの公開キーをコピー
  3. GitHubのサイト(ページ右上歯車アイコン→SSH Keys)に公開キーを登録
  4. 接続確認
$ ssh-keygen -t rsa -C "<github email address>"  # SSHキー生成
$ eval "$(ssh-agent -s)"                         # SSH Agentを起動
$ ssh-add ~/.ssh/id_rsa                          # SSH AgentにSSHキーを追加
$ cat ~/.ssh/id_rsa.pub                          # 公開キーをコピー
# ---- GitHubサイトに公開キーを登録 ----
$ ssh -T git@github.com                          # 接続確認
Warning: Permanently added the RSA host key for IP address '<ip address>' to the list of known hosts.
Enter passphrase for key '/<home directory>/.ssh/id_rsa':
Hi <username>! You've successfully authenticated, but GitHub does not provide shell access.

ネット環境や時間帯の問題かもしれないですが、GitHubへのアクセスが重くて大変でした。
接続確認の手順も最初はハングしてしまったようで、やり直してみたら上手くいきました。

GitHubリポジトリ作成とpush

以下の通りGitHubリポジトリを作成してpushします。

  1. GitHubサイトで「New repository」でリポジトリを作成
  2. リポジトリ追加とpush
$ git remote add origin https://github.com/<username>/first_app.git
$ git push -u origin master

今はコマンドの意味が何となくしか分からないのですが、時間が解決してくれることを信じて、今日はここまでにします。

Rubyからjavascriptを実行するExecJS

Ruby on Railsチュートリアルに沿って勉強中です。

  1. rails new
  2. Gemfileを編集
  3. bundle update && bundle install
  4. rails server
    • ここで以下のようなエラーが発生しました。
Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

チュートリアルには「JavaScriptランタイムがインストールされていないというエラーが表示された場合は、GitHubのexecjsページにあるインストール可能なランタイムを確認してください。Node.jsが特にお勧めです」とのことですが…。
あーありがち - Rubyのexecjsがすごい件」の説明やRubyGem.org「execjs | RubyGems.org | your community gem host」で何となく分かったつもりになりました。

ExecJSとは

  • Rubyスクリプトからjavascriptコードを実行するための仕組み
  • ExecJSがjavascriptエンジンを自動検索(=autodetect)する
  • サポートしているjavascriptエンジンは以下の通り
ExecJS supports these runtimes:

therubyracer - Google V8 embedded within Ruby
therubyrhino - Mozilla Rhino embedded within JRuby
Node.js
Apple JavaScriptCore - Included with Mac OS X
Microsoft Windows Script Host (JScript)

つまり、最初のエラーはExecJSがサポートするjavascriptエンジンがないのでインストールする必要があることを意味していたようです。
動作が速いと評判が良さそうなのでtherubyracerにします。

$ gem list therubyracer -b

*** LOCAL GEMS ***



*** REMOTE GEMS ***

therubyracer (0.12.1, 0.5.2 x86-darwin-10, 0.5.1 x86-darwin-9, 0.5.0 x86-linux x86_64-linux)
therubyracer-discourse (0.12.0)
therubyracer-freebsd (0.10.1)
$ gem install therubyracer
Fetching: ref-1.0.5.gem (100%)
Successfully installed ref-1.0.5
Fetching: libv8-3.16.14.7-x86_64-linux.gem (100%)
Successfully installed libv8-3.16.14.7-x86_64-linux
Fetching: therubyracer-0.12.1.gem (100%)
Building native extensions.  This could take a while...
Successfully installed therubyracer-0.12.1
3 gems installed
$ rails server
`autodetect': Could not find a JavaScript runtime. See https://github.com/sstephenson/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)

これで解決すると思いきや、また同じエラーが表示されてしまいました。
次にbundle installで試してみます。

$ gem uninstall therubyracer   # 一度元に戻すためにアンインストール
$ vi Gemfile                  # 最後に「gem 'therubyracer'」を追記
$ bundle install
......
Installing therubyracer 0.12.1 # インストールされたことを確認
......
$ rails s
=> Booting WEBrick
=> Rails 4.0.5 application starting in development on http://0.0.0.0:3000
=> Run `rails server -h` for more startup options
=> Ctrl-C to shutdown server
[2015-03-21 22:31:03] INFO  WEBrick 1.3.1
[2015-03-21 22:31:03] INFO  ruby 2.0.0 (2014-11-13) [x86_64-linux]
[2015-03-21 22:31:03] INFO  WEBrick::HTTPServer#start: pid=2906 port=3000

無事に起動したようです。

bundlerはアプリ環境ごとにgemを固定するための仕組みだと思います。
そのため、gemでtherubyracerをインストールしてもGemfile.lockで対象アプリのライブラリは固定されているため、GemFilesにライブラリを書いてbundle installする必要があったのではないかと思います。(違っていたらごめんなさい。)

bundle installとbundle update

  • bundle install
    • Gemfileに更新があれば、インストールされていないgemをインストールして、Gemfile.lockを更新
    • 更新がなければ、Gemfile.lockに従ってgemを更新
    • Gemfile.lockがなければGemfileに従ってgemの更新を行い、Gemfile.lockを作成
  • bundle update
    • Gemfileに従ってgemの更新を行い、Gemfile.lockを上書き

Ruby開発環境

まだ開発スタイルが定まっていないのですが、ここまで試行錯誤やってきた(まだトータル2,3日ですけど)ことを一度整理しておきます。

Linux仮想環境 on Windows

PCのメインOSはWindowsなのですが、Ruby開発はLinux仮想環境を用意して進めた方が良さそうです。

これには以下のメリットがあると思います。

  • 多くの開発者がこの環境を使用しているため、躓いたときに調べやすいはず
    • 感覚的にはUbuntu環境で開発している方が多いように感じました。
  • RVMによるバージョン管理であったり、それ以外にも開発ツールが充実していそう
    • これも多くの開発者がLinuxを使っているからでしょうけど。

その一方で、普段はWindows環境なので、当初は以下のデメリットがあると思っていました。

  • 仮想環境を構築するなんて難しくて大変そう
  • ミドルウェアのインストールなど躓くことが増えそう

仮想環境は一昔前と比べると簡単に構築できるようになっていましたし、何らかの問題が発生しても色々な方が同じ点で躓いて、ブログにメモしておいてくれるので何とかなりそうです。
幸い仕事でLinux環境に触れていたこともあり、ここまでは順調に進んでいます。

手順

  1. Windows/Hyper-Vで仮想環境を作成して、Ubuntuをインストール
  2. UbuntuSSHをインストールして、PuTTYから接続→以降はPuTTYから作業
    • Ubuntuのデスクトップで作業していたのですが、少し動作が遅い(画面がカクカク)してしまうので、WindowsからSSH接続して作業することにします。
    • IDEeclipseと思っていたのですが、SSH接続となってしまったので、viとかemacsとかに本格的に挑戦してみようかと思います。
  3. UbuntuRubyをインストール

Rubyインストール

Windows 8.1 + Hyper-Vによる仮想環境にUbuntu 14.04をインストールして、Gitをインストール、次はいよいよRubyRailsです。

Rubyインストール

$ sudo apt-get install curl
$ curl -L https://get.rvm.io | bash -s stable # 認証に失敗して次のgpgコマンドを指示されたので従う
$ gpg --keyserver hkp://keys.gnupg.net --recv-keys 40......
$ curl -L https://get.rvm.io | bash -s stable
$ source ~/.rvm/scripts/rvm # ログイン時に自動実行されるように.bashrcにも追記
$ rvm requirements          # 途中必要なライブラリをインストールするか聞かれるのでインストール
$ rvm install 2.0.0
$ ruby -v
ruby 2.0.0p598 (2014-11-13 revision 48408) [x86_64-linux]
$rvm list

rvm rubies

=* ruby-2.0.0-p598 [ x86_64 ]

# => - current
# =* - current && default
#  * - default
  • curlのコマンドを実行したところ、認証に失敗したのでgpgコマンドを実行するようにとのメッセージが表示されたため、gpgコマンドを実行した後でcurlを再実行

Railsインストール

$ which gem    # gemがインストールされているか確認
$ gem update --system 2.0.3
$ : > ~/.gemrc
$ vi ~/.gemrc # 次の通りに編集
$ cat ~/.gemrc 
install: --no-rdoc --no-ri
update:  --no-rdoc --no-ri
$ gem install rails --version 4.0.5

初めてのGit

Ruby on Railsチュートリアルによると、次はGitをインストールしなさいとのことです。
Gitも初めてです。

インストール

$ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext libz-dev libssl-dev
...
$ sudo apt-get install git
...
$ git --version
git version 1.9.1
$ git config --global user.name "<username>"      # ユーザ名登録
$ git config --global user.email "<mail address>" # メールアドレス登録
$ git config --global color.ui auto               # 出力のカラーリング

リポジトリ作成

$ mkdir mygit
$ cd mygit
$ git init                           # リポジトリ初期化
$ echo "sample" > sample.txt
$ git add .                          # ファイル追加
$ git commit . -m "<commit message>" # コミット
$ git log                            # コミットログ参照

このあたりはCVSSVNとほとんど同じようなので、違和感なく行けそうです。
CVSだとファイル単位のリビジョン、SVNだとコミット単位のリビジョンなどの概念的な違いについては分かっていませんが、時間のある時に読むか必要に迫られたらやるということにして次に進みたいと思います。

Window 8.1のHyper-VでUbuntuを動かす

Ruby on Railsチュートリアルに「Windowsの場合、Linuxが動作する仮想マシンを用意して、その仮想マシン上でRailsアプリケーションを開発することが好まれています。」とあって、どのような方法があるのか調べていたら、最近は簡単に仮想環境を実現できることを知りました。

仮想マシン作成

  1. 「コントロールパネル」→「プログラムと機能」→「Windowsの機能の有効化または無効化」にてHyper-Vを有効にする
    • 最初は「Hyper-V プラットフォーム」がグレーアウトしてチェックできない状態になっていたが、BIOSでCPUの設定を変更することで有効になった
  2. Ubuntuの仮想ハードディスクイメージを「仮想ハードディスクイメージのダウンロード | Ubuntu Japanese Team」からダウンロードして解凍
  3. Hyper-Vマネージャを起動して、仮想マシンを作成
    1. 先にネットワークのための仮想スイッチを作成(外部スイッチで作成)
      • ネットワークが繋がらなくて苦戦
      • 仮想スイッチを内部スイッチにして、仮想マシン側でIPを固定したりと色々試したが繋がらず
      • 外部スイッチに戻して、仮想マシン側をDHCPに戻しても繋がらず
      • 諦めて、そのまま放置して寝ることにしたら翌日繋がっていた…(←最近こんなの多いな)
    2. 「既存の仮想ハードディスクを使用する」で上記Ubuntuの仮想ハードディスクイメージを指定
  4. Ubuntuを起動

解像度の変更

  1. 「/etc/default/grub」の「GRUB_CMDLINE_LINUX_DEFAULT」を編集
    • 「video=hyperv_fb:1920x1080」を追記(すでにエントリーがある場合はスペース区切りで追加)
  2. /usr/sbin/update-grub2
  3. Ubuntuを再起動

Ubuntu 12.04では上記手順で解像度を変更できなかったため、Ubuntu 14.04で仮想マシンを作成して試すと1920x1080になりました。が、何だか不安定過ぎて実用に耐えられなさそうです。

初めてのRuby on Rails

ここ1年くらい仕事でFuelPHPFuelPHP.JP 日本語ドキュメント)というPHPフレームワークを使っていました。このフレームワークの採用を決定するまでに、PHPフレームワークを幾つか調査していたのですが、多くのPHPフレームワークの源流にRuby on Railsの影が見えます。

Rubyは10年くらい前から触っていて、Ruby on Railsが出始めた頃に一度だけ自動生成したことがあるのですが、その後はオレオレフレームワークでアプリを作っていたので、この辺でRubyアプリのデファクトを触っておきたいと思いました。

先月購入した「O'Reilly Japan - RとRubyによるデータ解析入門」もやりたいのですが、いったん寄り道します。特に何かを作る目的もないので、「Ruby on Rails チュートリアル:実例を使って Rails を学ぼう」に沿って進めてみます。

eclipse+RDT

これまでテキストエディタを使ってRubyのコードを書いていたのですが、IDEを触ってみようかと思いました。最近、仕事で使っているということもあって、まずはeclipseのシンプルなものということで、最新のeclipseにRDTプラグインをインストールしてみます。

手順

  1. Eclipse 日本語化 | MergeDoc Project」から「Eclipse 4.4 Luna」→「Pleiades All in One Platform / Full Edition」をダウンロード
  2. ダウンロードしたファイルを解凍して配置
  3. eclipseを起動後、「ヘルプ」→「新規ソフトウェアのインストール」で作業対象のプルダウンから「Luna - http://download.eclipse.org/releases/luna」を選択…するがサイトが見つからない的なエラーメッセージになる
    • 毎回eclipseを落とす度に嵌っているが、時間が掛かっているだけなのだろうか?
    • 悪戦苦闘しているうちに気付くとサイトにアクセスできるようになっている
    • 気長に待つのが吉
  4. フィルターに「Ruby」と入力すると「動的言語ツールキット - Ruby 開発ツール」が表示されるのでインストール
  5. 終わったらEclipseを再起動

Eclipse右上のパースペクティブにもRubyと表示されるようになったので、上手くいったのかなと思います。早速使ってみたいところですが、時間も遅いので今日はここまでにします。また来週…と思っているとまた違うことがしたくなっていたりするので、どうなることやら。

RubyによるMySQLデータエクスポート/インポート

サンプルDBデータを作成するのに、DBデータを「エクスポート→Excel編集→インポート」の流れで作業するのを想定したスクリプトを書いてみました。今後も使えるかもと思い、調べたことなどメモしておきます。

ざっくり仕様

  • メインのロジックはdata_loader.rbに実装
  • ダウンロード実行用に叩くためのexp.rbとアップロード実行用に叩くためのimp.rbを作成
  • exp.rbとimp.rbの引数にテーブル名(スペース区切りで複数指定可)を指定すると、指定されたテーブルのみエクスポートまたはインポート
    • 何も指定しない場合はすべてのテーブルを対象

困ったこと

  • Ruby(mysql2)からローカルのMySQLに接続できない
  • インポートファイルが空白だと、文字列項目だと空文字、数値項目だと0になってしまうがNULLにしたい
    • 「\N」にしておくとNULLになる
  • 項目のダブルクォートは使いたくないが、改行が入っていると困る
    • エクスポートしたテキストファイルからCtrl-AしてExcelに貼り付ける想定なので、改行のままだと扱いづらい…ということで、エクスポート時に改行を「<LF>」に置換、アップロード時に改行に戻すことに
  • MySQL上で「on Update CURRENT_TIMESTAMP」を設定しているタイムスタンプ項目はどう扱うのか?
    • 値を指定してもNULL、「\N」を指定してもNULLになってしまう…
    • インポートファイルから項目自体をなくしたら解決したので、タイムスタンプ項目はエクスポートの際に出力しないことに
  • アップロード用のワークファイルはCR+LFではなくLFで出力したい
    • ワークファイルを開く際のモードはバイナリモード(wではなくwb)を使う

ソースコード

data_loader.rb
# -*- coding: utf-8 -*-
require 'rubygems'
require 'mysql2'

class DataLoader
  MySQL = {
    :host => "127.0.0.1",
    :username => "<username>",
    :password => "<password>",
    :database => "<database>",
  }

  OutDir = "./out"
  InDir = "./in"

  def initialize
    @conn = Mysql2::Client.new(MySQL)
    @conn.query "set character set utf8"
  end

  def exp(args)
    puts "---- start"
    unless File.exist?(OutDir)
      Dir.mkdir(OutDir)
    end

    tables.each_with_index do |table, i|
      if args.empty? || args.include?(table)
        print "##{i + 1} #{table}"

        sql = "select * from #{table}"
        result = @conn.query(sql)

        out = File.open(OutDir + "/#{table}.dat", "wb")

        tmp = []
        result.fields.each do |fld|
          tmp << fld unless fld == "<timestamp>"
        end
        out.puts tmp.join("\t")

        result.entries.each do |rec|
          tmp = []
          result.fields.each do |fld|
            case fld
            when "<timestamp>"
              nil
            when "<create_at>", "<update_at>"
              tmp << "1970-01-01 00:00:00"
            when "<create_by>", "<update_by>"
              tmp << "system"
            else
              tmp << (rec[fld].instance_of?(String) ? rec[fld].gsub(/\r?\n/, '<LF>') : rec[fld])
            end
          end
          out.puts tmp.join("\t")
        end

        out.close

        puts " ---> #{result.entries.length} records exported"
      end
    end
    puts "---- end"
  end

  def imp(args)
    puts "---- start"
    tables.each_with_index do |table, i|
      if args.empty? || args.include?(table)
        file = "#{InDir}/#{table}.dat"
        if File.exist?(file)
          print "##{i + 1} #{table}"

          impfile = File.expand_path(File.dirname(__FILE__) + "/#{InDir}/_work_.dat")
          out = File.open(impfile, "wb")

          open(file, "r:utf-8") do |fh|
            while line = fh.gets
              tmp = line.split(/\t/)
              tmp.each_with_index do |item, i|
                case item
                when ""
                  tmp[i] = "\\N"
                when /<LF>/
                  tmp[i] = "\"" + item.gsub(/<LF>/, "\n") + "\""
                end
              end
              out.puts tmp.join("\t")
            end
          end

          out.close

          sql = "truncate table #{table}"
          @conn.query sql

          sql = %!load data local infile '#{impfile}' into table #{table} fields optionally enclosed by '"' terminated by '\\t' ignore 1 lines!
          @conn.query sql

          sql ="select count(1) as total from #{table}"
          result = @conn.query(sql)
          total = result.entries.first["total"]

          puts " ---> #{total} records imported"
        end
      end
    end
    puts "---- end"
  end

  private
    def tables
      sql = <<-EOT
select *
from information_schema.tables
where table_schema = schema()
order by table_name
      EOT
      buf = []
      @conn.query(sql).entries.each do |rec|
        buf << rec["TABLE_NAME"]
      end
      buf
    end
end
exp.rb
# -*- coding: utf-8 -*-
require File.dirname(__FILE__) + "/data_loader.rb"

DataLoader.new.exp ARGV
imp.rb
# -*- coding: utf-8 -*-
require File.dirname(__FILE__) + "/data_loader.rb"

DataLoader.new.imp ARGV