『Elixir/Phoenix初級③: フォーム構造体とチェンジセット』(OIAX BOOKS)本日発売開始

拙著『Elixir/Phoenix初級③: フォーム構造体とチェンジセット』(OIAX BOOKS)が本日発売になりました。価格は税込み1,944円。206ページです。

f:id:tkrd:20180404093239j:plain

残念ながら全国の書店には並ばないので、AmazonかOIAX BOOKSのウェブストアからお買い求めください。Kindle版もあります(税込み1,080円)。

前巻(『Elixir/Phoenix初級②)の出版からちょうど1年経過しました。もともとの計画では2017年夏には出そうと思っていたのですが、ソフトウェア開発の仕事が忙しくなってしまって、なかなか執筆時間を確保できませんでした。

前巻まではElixir 1.3とPhoenix 1.2を採用していたのですが、本巻の内容はElixir 1.6とPhoenix 1.3に基づきます。Phoenix 1.3になって、Webアプリケーションのディレクトリ構造と用語法が大きく変わりました。概要を知りたい方は、@shufo 氏のPhoenix 1.3のディレクトリ構造とContext - Qiitaを参照してください。

Phoenixの「チェンジセット」はとても面白い概念です。本巻ではそれほど深いところまでは解説できていないのですが、チェンジセットを使って実際にプログラムを書いてみれば、関数型言語でWebアプリケーションを開発する楽しさの一端が見えてくると思います。

著述スタイルは、前巻を踏襲しています。具体的なWebアプリケーション例として簡易予定表管理システムNanoPlannerを作りつつ、必要に応じてElixirの文法や各種関数の説明を加えていく、というスタイルです。初心者でも迷わないように、コマンドの実行やソースコードの変更については、できるかぎり省略せずに記述しています。プログラミング経験の長い方には、少し冗長に見えるかもしれません。


さて、本書は今月の22日に開かれる技術書典4に出品されます。私の会社(株式会社オイアクス)も参加を申し込んでいたのですが、残念ながら落選してしまいました。そこで、知人のブースを間借りさせていただくことにしました。「き09」の「神Excel(カミエクセル)」です。少し割引があるので、よろしくお願いいたします。

Windows Subsystem for Linux は Rails 入門環境には(多分)向かない【追記あり】

私は、Ruby on Rails の入門書の著者あるいは初心者向け講習会の講師として、Microsoft Windows というプラットフォームの取り扱いに悩まされてきました。

プログラミング入門者・初心者の間では Windows が主流ですが、プロの Rails エンジニアの多くは macOSLinux を使っています。RubyRails が急速に進化を遂げる中で Windows が取り残される、という状況がどうしても起きます。そのため、WindowsRails 開発環境を構築する過程でさまざまなトラブルが発生しがちです。

2014年刊行の実践Ruby on Rails 4 現場のプロから学ぶ本格Webプログラミングでは、VagrantVirtualBox を用いて CentOS または Ubuntu をインストールする方法を採用しました。

しかし、2015年刊行の改訂3版基礎 Ruby on Railsでは、初版・第2版と同様にRubyInstaller for Windowsが配布しているインストーラを使用することにしました。本書はプログラミング自体を初めて体験しようとする人々に読まれているので、Vagrant は少々ハードルが高いと考えられたためです。

さて、この文章を書いているのは2018年3月18日です。まもなく Ruby on Rails 5.2 がリリースされようという時期に当たります。『基礎Ruby on Rails』の改訂作業を始めようとしています。

私は普段の Ruby on Rails の開発業務で UbuntumacOS を主に使っていて、Windows 業界の動きはあまり追いかけていないのですが、Windows 10 上で UbuntuopenSUSE などの Linux ディストリビューションを動かせるという情報は目にしていました。そして、次の『基礎Ruby on Rails』の改訂では、この技術を利用できるといいなと漠然と期待を抱いてきました。

最初の発表当時、この技術は Bash on Windows と呼ばれていましたが、その後 Windows Subsytem for Linux (WSL) が正式名称となりました。

昨晩私は WSL を初めて試してみました。まず、コントロールパネルで WSL を有効にし、Microsoft Store から Ubuntu (無償)をダウンロードし、インストールしました。UbuntuRails の開発環境を構築するのはやや複雑な過程ではありますが、何度もやっていることなので難しくはありませんでした。

しかし、Rails のインストールが終わってから重大な問題に気付きました。Windows 上にインストールされたソフトウェアから WSL 上のファイルを操作できないのです。つまり、Windows 用のテキストエディタRailsソースコードを編集することはできません。Ubuntu 上のテキストエディタである nano とか vim とかを使うのなら問題はありません。でも、私はプログラミング入門者・初心者に nano や vim を勧める気にはなりません。AtomVisual Studio Code などのモダンなテキストエディタを使ってもらいたいです。これらなら初心者でもすぐに使い始められます。書籍の第1章や講習会の初日をテキストエディタの練習に費やしたくはありません。

もう一点、私の WSL に対する印象を悪くしたのは、Ubuntu のセットアップやライブラリ群のインストール・更新に予想外の時間がかかったことです。私のネットワーク環境や実施した時間帯がよくなかったのかもしれませんが、VirtualBoxUbuntu をインストールするのに比べてかなり進みが遅かったのです。

私は、WSL を採用することで環境構築の時間が短縮できると期待していたので、この結果にはかなり失望させられました。

以上のようなわけで、残念ながら『改訂4版基礎Ruby on Rails』で WSL を採用するのは難しそうだという結論になりました。

[追記] 2018-03-19

この文章をポストした翌日、もう少し可能性を探ってみようと思い立ちました。

基本的なアイデアは、「Windows側の特定のフォルダ X とWSL側の特定のフォルダ Y を自動で同期する」というものです。これができるのなら、Windows 側のエディタで X 上のファイルを編集し、WSL上で Rails サーバーを起動し、Windows 側のブラウザでアクセスする、という仕組みが可能になります。

手動でふたつのフォルダを同期するのは難しくありません。WSLのシェル上で

$ rsync -a /mnt/c/Users/kuroda/rails/ ~/rails/

というコマンドを実行すれば、Windows 上の kuroda ユーザーのホームディレクトリにある rails フォルダでの変更内容が WSL 側の ~/rails ディレクトリに反映されます。

しかし、ふたつのフォルダを自動で同期するとなると、話は複雑になります。Linux には lsyncd という便利なツールがあり、これをデーモン(daemon)として常駐させておけば、フォルダ同士が自動で同期されます。

しかし、現行の WSL は「デーモン」に対応していないのです。

Microsoftによる2017年12月4日の発表によれば、デーモンをサポートする方向で WSL の開発が進んでいるようで、2018 年春に予定されている次の Windows 10 のアップデートで実現するかもしれません。

けれども、『改訂4版基礎Ruby on Rails』の刊行も2018年初夏を予定しているので、原稿の準備や動作確認のことを考えるととても間に合わないそうもありません。

やはり、現時点では「Windows Subsystem for LinuxRails 入門環境には(多分)向かない」という結論になってしまいます。

[追記] 2018-04-15

Qiitaで@stkdevさんが書かれた記事RailsをWindows Subsystem for Linuxで動かしてみるを読んで、私が重大な見落としをしていたことに気付きました。

Windows側からWSL側のファイルを操作できないのですが、逆は可能なのです。つまり、RailsアプリのソースコードWindows側の適当なフォルダ上で作成すれば、Windows側のテキストエディタで読み書きできるし、WSL側でサーバーとして起動することもできる、というわけです。

まったく目が曇っていました。

具体的な手順は、こんな感じになります。Windowsユーザーの名前を kuroda とすれば、まず Windows 側のホームディレクトリ直下に rails というフォルダを作成します。それは、WSL側からは /mnt/c/Users/kuroda/rails として見えるので、WSLのシェル上で

$ ln -s /mnt/c/Users/kuroda/rails ~/rails

のようにシンボリックリンクを設定できます。続いて、WSLのシェル上で、Railsアプリを生成します。

$ cd ~/rails
$ rails new foo_bar

多分、以上の考え方で問題ないでしょう。『改訂4版基礎Ruby on Rails』の原稿を書き直す方向で検討したいと思います。

[追記] 2019-02-20

https://forest.watch.impress.co.jp/docs/news/1170221.html によれば、2019年4月に予定されている Windows アップデートにより、WindowsからWSL側のファイルをアクセスできるようになるそうです。

macOS/Homebrewで複数バージョンのPostgreSQLを共存させる方法(2018年版)

複数の PostgreSQL クラスタ(インスタンス)を起動するで書いたように Ubuntu 環境では、普通に apt-get でインストールするだけで複数バージョンのPostgresqlを共存させる状況が整います。

しかし、macOS/Homebrew 環境ではちょっとした準備作業が必要となります。以下、その概略を説明します。

【重要な注意】 以下の手順では、いずれのコマンドも sudo を付けずに実行してください。

【参考資料】 Macで複数のバージョンのPostgresqlを共存させる -- itmammoth 氏による 2016 年 3 月の記事。

SDK Headersのインストール(2019年1月追記)

macOS 10.14 (Mojave) を利用している場合は、次の手順で SDK Headers をインストールします。

  • Finderで「ライブラリ」→「Developer」→「CommandLineTools」→「Packages」を開く。
  • macOS_SDK_headers_for_macOS_10.14.pkg」をダブルクリックしてインストール。

【参考資料】

インストール済みの PostgreSQL を無効化

まず、現在の環境に PostgreSQL がインストールされているかどうかを確認します。

$ brew search postgresql

リストの中にある postgresql の右に ✔ アイコンが付いていればインストール済みです。次のコマンドで無効化します。

$ brew unlink postgresql

リポジトリの追加

リポジトリ petere/homebrew-postgresql を追加します。

$ brew tap petere/homebrew-postgresql

postgresql-common のインストール

$ brew install postgresql-common

PostgreSQL のインストール

インストール可能な PostgreSQL のバージョン番号は brew search postgresql コマンドで調べられます。表示されたリストの中に petere/postgresql/postgresql@X という項目があれば、バージョン X の PostgreSQL がインストール可能です。

例として、バージョン 9.6 と 10 をインストールしてみましょう。

$ brew install postgresql-9.6
$ brew install postgresql-10

もう一度 brew search postgresql コマンドを実行し、petere/postgresql/postgresql@9.6 および petere/postgresql/postgresql@10 の右側に ✔ アイコンが付いていることを確認してください。

クラスタの作成

ここから先は Ubuntu 環境の場合とほぼ同じです。ただし、データディレクトリ、設定ファイルの置き場所、データベース postgres の owner など、細かな違いがあります。繰り返しになりますが、すべてのコマンドを sudo なしで実行してください(重要)。

さて、PostgreSQL 用語の「クラスタ」とは、ひとつの PostgreSQL サーバーインスタンスによって管理される複数のデータベースの集合体を意味します。ひとつの macOS 上で複数個のクラスタを同時に稼働させることができます。

PostgreSQL 9.6 のクラスタ mainbackup を作成します。

$ pg_createcluster 9.6 main
$ pg_createcluster 9.6 backup

さらに、PostgreSQL 10 のクラスタ main を作成します。

$ pg_createcluster 10 main

合計で 3 個のクラスタが作られたことになります。クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます(一行が長いので一部省略しています)。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 down   kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

3番目の Port 列に着目してください。それぞれのクラスタに対応するサーバーインスタンスがどのポートを listen するかが書かれています。なお、5番目の Owner 列には、現在 macOS にログイン中のユーザー名が表示されます。

クラスタの起動

PostgreSQL 9.6 のクラスタ main を起動します。

$ pg_ctlcluster 9.6 main start

クラスタの状態を確認します。

$ pg_lsclusters

すると、次のような結果が表示されます。

Ver Cluster Port Status Owner  Data directory...
9.6 main    5433 online kuroda /usr/local/var...
9.6 backup  5434 down   kuroda /usr/local/var...
10  main    5435 down   kuroda /usr/local/var...

1 行目の Satus 列の値が down から online に変化しています。

同様に、他の 2 個のクラスタも起動します。

$ pg_ctlcluster 9.6 backup start
$ pg_ctlcluster 10 main start

psql で接続確認

PostgreSQL 10 のクラスタ mainpsql で接続してみましょう。

$ psql --port=5435 postgres

--port オプションでポート番号を、接続先データベースとして postgres を指定しています。初期状態で存在する唯一のデータベースが postgres です。オーナーは pg_createcluster を実行したユーザーです。

次のように出力されれば、成功です。

psql (10.2 (Homebrew petere/postgresql))
Type "help" for help.

postgres=#

クラスタの停止

PostgreSQL 9.6 のクラスタ main を停止するには、次のコマンドを実行します。

$ pg_ctlcluster 9.6 main stop

ポート番号などの変更

クラスタの設定ファイルは /usr/local/etc/postgresql ディレクトリの下にあります。PostgreSQL 10 のクラスタ main の設定ファイルは、/usr/local/etc/postgresql/10/main ディレクトリにあります。

ポート番号を変更したい場合は、エディタで postgresql.conf を開き、port = で始まる行を探して値を書き換えてください。設定を反映させるには、クラスタの再起動が必要です。

PostgreSQL: template1 のエンコーディングを UTF-8 に変更する

PostgreSQL におけるデータベース作成とは、既存のデータベースの複製を作ることです。デフォルトでは template1 という名前のデータベースが複製元となります。

さて、PostgreSQLのインストール直後に template1エンコーディングSQL_ASCII になっていることがあります。

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | SQL_ASCII | C           | C           | 

Web アプリケーションのバックエンドとして使う場合、エンコーディングとして UTF-8 を採用することが多いので、このままだと不便です。

psql のコンソールで次のステートメントを発行すれば、template1エンコーディングUTF-8 に(ついでに、ロケールをに ja_JP に)変更できます。

UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
DROP DATABASE template1;
CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UTF8'
  LC_COLLATE = 'ja_JP.UTF-8' LC_CTYPE = 'ja_JP.UTF-8';
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';

結果は、この通り:

postgres=# \l
                                    List of databases
     Name     |  Owner   | Encoding  |   Collate   |    Ctype    |   Access privileges   
--------------+----------+-----------+-------------+-------------+-----------------------
 postgres     | postgres | SQL_ASCII | C           | C           | 
 template0    | postgres | SQL_ASCII | C           | C           | =c/postgres          +
              |          |           |             |             | postgres=CTc/postgres
 template1    | postgres | UTF8      | ja_JP.UTF-8 | ja_JP.UTF-8 | 

Ubuntu: lsyncd によるディレクトリのリアルタイム同期(追記あり)

目標

  • Ubuntu 16.04 Server がインストールされた 2 台のサーバー host1host2 がある。
  • host1host2 の間で /home/foo/share ディレクトリの内容を常に同期する。
  • 一般ユーザー(root 以外のユーザー)で rsync コマンドを実行する。

準備作業

RSA鍵ペアの作成

foo ユーザーで host1host2ssh でログインし、それぞれ次のコマンドを実行する。

$ ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa.rsync

RSA鍵公開鍵を設置

  • host1~/.ssh/id_rsa.rsync.pub の中身を host2~/.ssh/authorized_keys の末尾に貼り付ける。
  • host2~/.ssh/id_rsa.rsync.pub の中身を host1~/.ssh/authorized_keys の末尾に貼り付ける。

注意: ~/.sshパーミッションが 700 であり、~/.ssh/authorized_keysパーミッションが 400 または 600 であることを確認すること。

接続確認

$ ssh -i ~/.ssh/id_rsa.rsync host2 # host1 から
$ ssh -i ~/.ssh/id_rsa.rsync host1 # host2 から

rsync コマンドの動作確認

host1 で次のコマンドを群を実行する。

$ mkdir ~/share
$ echo TEST ~/share/test.txt
$ rsync -av -e ssh ~/share/ foo@host2:/home/foo/share

~/share/ の末尾のスラッシュ(/)は必須である。

host2/home/foo/share ディレクトリに test.txt というファイルができていればOK。

lsyncd のセットアップ

ソフトウェアのインストール

$ sudo apt-get install -y lsyncd

設定ファイルの作成

/etc/lsyncd/lsyncd.conf.lua を作成。

settings {
        logfile    = "/var/log/lsyncd.log",
        statusFile = "/tmp/lsyncd.stat",
        delay        = 1
}

sync {
        default.rsync,
        source="/home/foo/share/",
        target="foo@host2:/home/foo/share/",
        delete=false,
        rsync = {
          rsh = "/usr/bin/ssh -i /home/foo/.ssh/id_rsa.rsync -o UserKnownHostsFile=/home/foo/.ssh/known_hosts"
        }
}

注意事項:

  • delay の値(デフォルト: 15)は、ファイル更新イベント発生から rsync 実行までの遅延時間(秒単位)である。リアルタイム性を追求するなら値は小さいほうがよいが、ファイル更新イベントが頻発するサーバーでこの値を小さくし過ぎると、かえって更新に時間がかかるかもしれない。
  • source の末尾のスラッシュ(/)は必須である。
  • host2 側の lsyncd.conf.lua では、target の値を "foo@host1:/home/foo/share/" とする。
  • delete=false については本稿末尾の「備考」を参照。
  • -o UserKnownHostsFile-... の記述を省略すると、Host Key Verification Failed というエラーが発生する。

lsyncd の起動

lsyncd を起動。

$ sudo systemctl restart lsyncd

動作確認

  • host1/home/foo/shared ディレクトリに新しいファイルを追加し、host2/home/foo/shared ディレクトリにそのファイルが転送されることを確認。
  • host2/home/foo/shared ディレクトリに新しいファイルを追加し、host1/home/foo/shared ディレクトリにそのファイルが転送されることを確認。

うまく行かない場合は、/var/log/lsyncd.log を見て調べる。

備考

本稿で使用した lsyncd の設定では delete=false オプションを使用しているため、host1 または host2 でファイルが削除されても、もう一方の側でそのファイルは削除されない。

ファイルが削除される可能性のある環境で lsyncd による「双方向同期」を使用する場合の注意点については、lsyncdで双方向同期するなら、delete='running' がいいを参照せよ。


追記

「備考」で触れたが、lsyncd で双方向同期を行う場合、ファイル削除の扱いが難しい。

host1 がダウンしている間に host2 に追加されたファイル X があるとき、host1 の再起動時に X が削除される可能性がある、ということだ。

この問題を回避するため、参照したブログ記事では「lsyncd の起動時にファイルの同期をしない(delete="running")」という設定を勧めている。

しかし、そもそもなぜファイルを削除するのかを考えると、この回避策でよいだろうか。

host1 がダウンしている間に host2 でファイル Y が削除されたとする。「lsyncd の起動時にファイルの同期をしない」設定の場合、host1 の再起動後も host1 上でファイル Y が残り続けることになる。

おそらく、ファイル Y を削除する目的は、ディスクスペースの節約かファイル Y の内容を残したくない(内容がまずい)かのどちらかだろう。

つまり、「lsyncd の起動時にファイルの同期をしない」設定では、この目的が達成されない。

とすれば、正しい回避策は、「lsyncd の起動時にファイルの同期をするが、ファイルの削除はしない(delete=false)」設定にしておいて、ファイル Y を削除したいときにはファイル Y を空にする(ファイルサイズを 0 にする)運用をすることではないだろうか。これならば、host1 が復活したときに、host1 上のファイル Y が消える。

ちなみに、サイズが 0 のファイルも一定のディスクスペースを占めるし、あまりに数が多いと「inode枯渇」という別の問題を引き起こす。

だから、ファイル削除が頻繁に行われる環境では、サイズが 0 のファイルを削除する仕組みを別途考えたほうがいいだろう。

例えば、毎朝 4:01 に host1host2 の両方が動いていることを確かめた上で、4:00 以前に書き換えられたサイズ 0 のファイルをすべて削除するような処理を cron にやらせればいい。

なお、この「追記」の内容は筆者が頭の中で考えたことに過ぎず、実績があるわけではない。参考にされる方は、この点に留意していただきたい。

Ubuntu: init system を systemd から upstart に変える

Ubuntu 16.04 がインストールされている私の職場PCがハードディスクの故障で動かなくなり、修復のためにいろいろやっている過程で、

  • ブート時のメニューで「Ubuntu」を選んでも起動しない。
  • 「Advanced options for Ubuntu」を選んでから「upstart」という文字を含むエントリーを選ぶと起動する。

という現象に遭遇した。init system を systemd から upstart に変えればよさそうだ。

GRUBのメニューを自分で書き換えればいいのかなと思ったが、正しい手順は以下の通りであった。

sudo apt-get install upstart-sysv
sudo update-initramfs -u

参考資料:

PostgreSQL: キャッシュした計画は結果型を変更してはなりません

PostgreSQL を用いた Rails アプリケーション開発で

キャッシュした計画は結果型を変更してはなりません

あるいは

cached plan must not change result type

というエラーメッセージに遭遇することがある。

解決法は簡単で、Rails サーバーを再起動すればよい。

なぜこのエラーが出るのか知りたい方は、Qiitaの次の記事を読むとよい。

cached plan must not change result type なんてエラーが出たら

簡単に言えば、Rails サーバーを起動したままマイグレーションを実行すると、この現象が出る。