僕は発展途上技術者

Rails 7アプリ on Heroku でPostGISを使い、外部APIを使わず緯度経度から県名を導く

PostGISを扱う練習として、HerokuでRails 7アプリからPostGISを使えるように設定し、緯度経度を与えたときに、その場所を含む県名を取得して表示するようなサービスを作ってみました。

DockerでRails 7アプリを動かす

第一歩として、ローカル環境でRails 7のアプリを動かします。Docker DesktopなどをインストールしていてDockerのセットアップが済んでいることが前提です。

ryanwi/rails7-on-docker

にあるコードをgit cloneし、READMEにある通り、セットアップをおこなって動かします。

http://localhost:3000 にアクセスすれば、以下のようにトップ画面が表示されます。

PostGISを有効にする

以下の3箇所を修正してローカルのDocker環境でPostGISを有効にします。

Gemfileに以下を追加します。

gem 'activerecord-postgis-adapter'

config/database.ymlのadapterをpostgisに変更します。

default: &default
  adapter: postgis

docker-compose.ymlでdbホストのイメージを変更します。

  db:
    image: mdillon/postgis

またPostGISを有効にするためのマイグレーションファイルを作成します。

docker-compose exec web rails g migration AddPostgisExtensionToDatabase

で生成したマイグレーションファイルの中身は

class AddPostgisExtensionToDatabase < ActiveRecord::Migration[7.0]
  def change
    enable_extension 'postgis'
  end
end

とします。

これらの変更をおこなった上で、再度docker-compose buildから環境の再構築をおこなうのですが、その前にdocker volume rmで、最初に作ってしまったデータベース用のdockerボリュームを削除しておきます。

docker volume rm rails7-on-docker_pg_data

これを行った上で、docker-compose buildから環境の再構築をおこないます。

再構築が終わり、再びdocker-compose upでコンテナを起動したら、

docker-compose exec db bash

の後、

psql -U postgres railsondocker_development

でpostgresに接続後、SELECT postgis_version(); を実行して、以下のようにpostgisのバージョン情報が出力されれば、PostGISの設定が有効となっています。

postgres=# SELECT postgis_version();
            postgis_version            
---------------------------------------
 2.5 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
(1 row)

Herokuで動かす

ここまでのコード変更は自分のGitHubリポジトリにコミット済みであることが前提で、Herokuで動かす手順は以下の通り。

GitHubリポジトリに接続し、

Automatic Deploysを有効にします。

config/database.ymlのproductionの部分を以下の通り書き換え、

production:
  <<: *default
  url: <%= ENV.fetch('DATABASE_URL', '').sub(/^postgres/, "postgis") %>

git pushすると、自動的にHerokuにデプロイされます。

最初、db:migrateが必要なので、

heroku run rails db:migrate -a <アプリ名>

を実行します。

heroku pg:psql -a <アプリ名>

でHerokuのDB(postgres)に接続し、ローカル環境のときと同様、SELECT postgis_version(); を実行して、PostGISの設定が有効となっていることを確認します。

緯度経度情報を保存するPointモデルを作成

緯度と経度、そして県名を保存できるPointモデルを操作するscaffoldを作成します。

docker-compose exec web rails g scaffold Point 'lat:decimal{10,2}' 'lng:decimal{10,2}' prefecture:string

migrationを実行します。

docker-compose exec web rails db:migrate 

県のポリゴンデータ

県のポリゴンデータを作成するための準備として市区町村のポリゴンデータをPostGISに取り込みます。

国土交通省の国土数値情報ダウンロードのページから「全国」のデータをダウンロードし解凍します。(令和3年のデータファイルはN03-20210101_GML.zip)

国土数値情報 | 行政区域データ

解凍したフォルダN03-20210101_GMLはアプリ直下のdbフォルダの下に置いておきます。

docker-compose.ymlを以下のように編集し、docker-compose buildで、コンテナを再構築し、上記フォルダにコンテナからアクセスできるようにしておきます。

  db:
    image: mdillon/postgis
    env_file:
      - .env/development/database
    volumes:
      - .:/usr/src/app <-- この行を追加
      - db_data:/var/lib/postgresql/data
docker-compose exec db bash

にdbホストに接続し、シェープファイルからSQLファイルを生成するためのコマンドshp2pgsqlを使って

shp2pgsql -c N03-20210101_GML\\N03-21_210101.shp shapes > shapes.sql

で全国の市区町村のポリゴンデータをPostGISに取り込むためのSQLファイルを作成します。

次に

psql -U postgres railsondocker_development < shapes.sql

を実行して、ポリゴンデータをshapesテーブルに取り込みます。

次に

psql -U postgres railsondocker_development

でpostgres接続後、

CREATE TABLE prefectures AS 
SELECT 
n03_001 AS pref_name,
ST_Multi(ST_Union(geom)) AS geom
FROM shapes
GROUP BY n03_001;

を実行して、各都道府県に属する市区町村のポリゴンデータを結合して、都道府県ごとのポリゴンデータをprefecturesテーブルに抽出します。

できあがったprefecturesテーブルをsqlファイルにエクスポートします。

pg_dump -U postgres --table prefectures railsondocker_development > prefectures.sql

Heroku 上では、このprefectures.sqlだけを読み込みます。

heroku pg:psql -a <アプリ名> < prefectures.sql

ポリゴンデータから緯度経度が所属する都道府県を導く

緯度、経度を入力し、Pointのデータを保存したときに、どの都道府県のポリゴンデータにその緯度・経度が含まれるかを調べ、その結果をprefectureにセットします。

以下の通り、Pointモデルのbefore_saveにその処理を定義します。

class Point < ApplicationRecord
  before_save :set_prefecture

  private
  def set_prefecture
    # 県名を取得
    res = ActiveRecord::Base.connection.select_all("SELECT pref_name FROM prefectures WHERE ST_Within(ST_GeomFromText('POINT(#{lng} #{lat})'), geom);")
    self.prefecture = res.first["pref_name"]
  end
end

Pointの入力フォームでprefectureを入力する必要はないので、その部分を削除します。

http://localhost:3000/points/new に削除し、たとえば以下のように緯度、経度を入力します。

Create Pointボタンをクリックすると、以下のようにその緯度、経度が属する都道府県が保存され、表示されます。

このとき、Geocodingなどの外部APIにアクセスしていないので、高速に検索できています。

参考リンク

Kaggle

Kaggleという機械学習のコンペのプラットフォームがある。留学中の長男がこのコンペの一つで順位が確定し、銅メダルを取ったとのこと。参加者3000超のうち、上位0.2%が金、5%が銀、10%が銅を獲得できるのだそうだ。犬や猫の写真が与えられ、どれだけ可愛く見えるか数値で表されたその人気度を機械学習で推測するプログラムを書くというのがコンペの内容。マレーシアにあるPetFinder.myという保護犬、保護猫と引き取ってくれる飼い主とをマッチングさせるサービスで、写真を掲載するときに使われることも目的としている(プログラムや使われた学習済モデルは公開されるので、上位のモデルは実際に使われるのかもしれない)。人間でやったら炎上しそうな内容ではあるが、保護猫、保護犬をできるだけ可愛くみせることで、引き取ってくれる飼い主をより多くマッチングさせることができるというわけだ。

機械学習には興味があるもののコンペに出られるほどの専門知識を僕は持ち合わせていないので、LINEで相談されてもろくなアドバイスを返せてはいなかったのだけれど、コンペ最終日まで毎日変わるリーダーボード上の息子の順位を一喜一憂しながら眺めるのはなかなか楽しい。リーダーボードというのは順位表で、最終日まではテストデータとして犬や猫の写真のたとえば25%というように一部しか与えられていない。それらの写真と人気度が与えられているので、写真やそのほかにも与えられた情報を元に人気度を導くアルゴリズムや機械学習モデルをうまく作り出す。そして、コンペ最終日を過ぎると、それまで公開されていなかった残りの本番用データ75%に対して適用し、その結果で最終順位が決まる。この仕組みがなかなか面白く、最後まで順位がわからなくてハラハラドキドキする。実際、最終日手前までは長男の順位は銀メダル圏内だったのだが、それは予選みたいなもので、あくまで最終結果だけがすべてだ。ツイートのなかで shake-down とあるのは、最終結果でそれまでの順位より下がることを意味する Kaggle 用語のようだ。逆に順位が上がることを shake-up というのだそうだ。shake-down したが、なんとか銅メダル圏内に残ったという形だ。さらに、最終日後しばらくの期間、不正があったかどうか(まったく同じプログラムが提出されていないかどうかなど)が調査されるらしく、数日経った今日、順位が確定したらしい。

ほかにも、参加は一人でもあるいはチームでもできるらしく、チームの場合は、たとえばそれぞれでモデルを作成し、予想した結果の平均を取ったり、多数決で決めた結果を採用するといったアンサンブルという手法があったりするのだそうだ。アルゴリズムやプログラムの詳しい内容はわからなくても、ロボコンや e-sports の競技を観戦、応援しているような楽しさがある。

長男が留学でシアトルに発ってから約2週間が経とうとしている

長男が留学でシアトルに発ってから約2週間が経とうとしている。

ブログシステムを Next.js で自作して、近況を報告してくれるようになったので、僕も思い出したようにこの自作ブログを久々に更新しようと思う。

心配する僕の家族(長男にとっては祖父母)にブログのリンクを送ったら、父親から、

約50年前に一人でアメリカ、メキシコ経由でミラノからペルージャに行き、下宿屋を探して苦労したことをついこの間のことのように思い出しました。

という感想が送られてきた。僕は1歳から約2歳までミラノで育ち、その後はパリで8歳になる少し前まで育ったのだが、初めて聞くエピソードだった。

僕も、約20年前、アメリカ、カリフォルニアに妻と引っ越すことになり、着いてすぐにソーシャルセキュリティオフィスでSSNを取得したり、Wells Fargo に行って約2時間もかかって銀行口座を開いてもらったりした苦労を思い出す。

でも、父の時代からももちろんそうだが、この20年でもおもにスマホとインターネットの力で格段に便利になっていることを痛感する。

出発前、Amazon ですでにアメリカのプリペイドのSIMカードを買っていたので、空港に着いたらすぐ SIM カード入れ替えて、無事到着の一報を LINE で受け取ることができる。

僕のときは、一晩かけて日本ではやっているらしい音楽を Napster などからダウンロードしてきて DVD に焼いていたりしたが、いまはストリーミングサービスで気軽に聴ける。

郊外に住んでいたので、1時間近くかけてサンフランシスコのジャパンセンターにある紀伊國屋書店まで行って、1.5倍から2倍近くする日本の本を購入していたりしたのだが、今は電子書籍を定価でいつでも購入することができる。

AAA(日本のJAFにあたるロードサービス)に入会すると、アメリカ中の地図を無料で取得できるということで、旅行で行く可能性もあるところも含め、住む場所近辺の地図などを取り寄せ、出かけるときにはいつもそれらの地図と Mapquest で道案内を調べてプリントアウトしたものが欠かせなかったのだが、いまでは Google Maps があればどこにでも行けるとのこと。

うらやましい。

とはいえ、やはり異国での生活というのは苦労の連続だ。これからたくさん苦労するのだろうなと思うと、心配でもあり頼もしくもあり、複雑な気分だ。

ファン歴30年の僕が選ぶデビュー30周年スピッツの曲ベスト10

スピッツのメジャー・デビューは1991年3月25日で、それまで大学の受験勉強にほぼすべてのリソースを振り向けていたあとに解放された僕の乾いた心にすっと入ってきたのがスピッツの曲だった。最初のアルバムの「スピッツ」と2枚目の「名前をつけてやる」は、楽器やバンドに興味を持っていた弟が買ってきたものだった。「このバンドが良い」とすすめてきて、僕もミニコンポが置いてあった弟の部屋でその2枚を良く聞いていたものだ。3枚目のアルバムの「惑星のかけら」は大学の生協だけに許される、発売日に10%引きで買った。卒業論文の執筆、研究で毎晩のように研究室で徹夜で過ごしていたときは、「ロビンソン」がヒットする前で、その当時まだほとんど知られていないスピッツのアルバムをヘビーローテーションでかけていたら、同じ研究室の同学年の仲間たちが気に入ってくれて、論文を書き上げたあとの打ち上げでカラオケに行ったときにはみんなでスピッツの曲を歌っていた。

僕は自分が好きだと思ったものは、ほぼだいたいそのままずっと好きのままでいることが多い。映画なら5歳のときに初めて観た「スター・ウォーズ」がずっと好きだ。スピッツは今年メジャー・デビュー30周年だそうで、ということは僕はファン歴30年だ。30年というのは今までの人生のほぼ3分の2にあたる。30年間、どの瞬間をとってもスピッツの曲を聞いていたので、スピッツの曲を聞くとそのときの自分を思い出す。

30周年を記念して、 スピッツデビュー30周年 みんなで作った!スペシャルプレイリスト - スピッツ が発表された。どれも納得の曲だが、スピッツの曲はどれも外れがない。せっかくなのでこのベスト30に入っていない僕のベスト10を挙げてみた。

  • 魔女旅に出る
  • 夏の魔物
  • ウサギのバイク
  • ヒビスクス
  • 群青
  • エンドロールには早すぎる
  • 夕陽が笑う、君も笑う
  • 快速
  • ハニーハニー
  • 謝謝!

40周年、50周年のときにこのリストがどうアップデートされるのか楽しみだ。

手軽にVPNネットワークを構築できるTailscaleを使い、ngrokなしでローカルで開発しているサイトに外部からアクセスする

Webシステムを開発していると、ローカルで開発中のサイトに外部からアクセスして確認したいという必要が良くでてきます。

ngrokやserveoなしでローカルで開発しているサイトに外部からアクセスする方法 では ngrok の代わりに ssh と caddy を使った方法を紹介したのですが、Rebuild: 295: Point of No Return (hak) で紹介されていて知った VPN ネットワークを手軽に構築できる Tailscale を使うと、とても簡単に実現できたのでその方法を紹介します。

Tailscale で VPN ネットワークを構築

Tailscale を使って VPN ネットワークを構築するのはとても簡単です。まず、ダウンロード画面より、各 OS 向けのインストーラーをダウンロードしてインストールします。Tailscale を起動し、メールアドレスでアカウント登録してログインするか、Google または Microsoft アカウントでログインするだけです。僕は Google アカウントでログインする方法を選んだのですが、Mac と Android で同じアカウントでログインすれば、それだけで同じ VPN ネットワークに接続できるようになります。

Mac 上で開発している Web アプリを起動しておき、Android にインストールした Tailscale を開くと以下のように、同じ VPN ネットワークに接続している各PCや端末が表示されるので、その IP アドレスをコピーしてブラウザの URL 欄に貼り付けるだけで接続できるようになります。

ローカルで開発中のサイトに外部からアクセスできるようにするには、これだけで十分です。以降は、たとえば位置情報を使ったサービスの場合は、スマホから https でアクセスして確認する必要があるため、僕が使っている Rails + Puma の環境で SSL/HTTPS を有効にする方法を備忘録としてメモしています。

Rails + Puma で SSL/HTTPS を有効にして、https で接続できるようにする

ほぼ Rails5 + pumaのローカル環境でSSL/HTTPSを有効にする の手順通りです。

$ openssl genrsa 2048 > server.key
$ openssl req -new -key server.key > server.csr
$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt

で各種鍵や証明書を作成したら、

$ mkdir config/keys
$ mv server.* config/keys/

で config/keys 以下に移動します。

config/puma.rb を以下のように編集して、通常の http 接続は 3000 番、https 接続は 9292 番ポートで受けるようにします。

port ENV.fetch('PORT') { 3000 }

if ENV.fetch('RAILS_ENV') { 'development' } == 'development'
  ssl_bind '0.0.0.0', '9292', {
    key: 'config/keys/server.key',
    cert: 'config/keys/server.crt',
    verify_mode: 'none'
  }
end

puma をいったん停止し、

$ bundle exec puma -C config/puma.rb

で起動すれば、たとえば、Web アプリを起動している PC の VPN 上での IP アドレスが 100.101.102.103 であれば、スマホのブラウザから

http://100.101.102.103:3000

でアクセスすれば http 接続に、

https://100.101.102.103:9292

でアクセスすれば https 接続できるようになります。

GitHub Actions で独自 Scratch を動かす

機械学習を始め様々な機能を拡張機能として使えるようにしたカスタマイズされたScratch を Stretch3 と名付けて GitHub 上で公開しています。自分が開発した拡張機能に加え、Scratch コミュニティの他の開発者の方々が作った拡張機能も使えるようにしており、各々の拡張機能のバージョンアップがあるたびにその変更を取り込みビルドし直して公開するのが面倒だったため、GitHub Actions を使って自動的にビルドするようにしています。独自の拡張機能を作って追加した Scratch を公開したいという場合に参考になると思うので、その方法を紹介したいと思います。

なお、Scratch を自分のマシン上で動かす方法や、改造方法、拡張機能の作り方についてもっと知りたいという方は、「Scratch を改造しよう - 大人のためのScratch」を参照してください。有料コンテンツとして公開しているのですが、Scratch を自分のマシンで動かしブロックの見た目を少しだけ変えるところまでの最初の3章だけは無料で公開しています。

テンプレートプロジェクトを fork し、git clone する

独自の拡張機能を作って追加した Scratch を簡単に公開できるようにテンプレートとなるサンプルプロジェクトを用意したので、これを fork して git clone します。

% git clone git@github.com:<あなたのGitHubアカウント>/scratch3_extension_template.git

scratch_extension_template 以下のフォルダ構成は以下の通りです。

scratch_extension_template
├── install.sh - 各ファイルを適切な場所に配置するインストールスクリプト
├── scratch-gui
│   └── src
│       └── lib
│           └── libraries
│               └── extensions
│                   └── hello - 拡張機能の名前。ここでは hello と仮で名付けています。
│                       ├── hello-small.png - 拡張機能一覧に表示されるアイコン画像
│                       └── hello.png - 拡張機能一覧に表示されるバナー画像
└── scratch-vm
    └── src
        └── extensions
            └── scratch3_hello - scratch3_<拡張機能名> という名前でフォルダを用意する。
                └── index.js - 拡張機能本体

GitHub Actions で自動ビルド

このプロジェクトに git push するたびに、自動ビルドをおこなう GtiHub Actions が実行されるのですが、ビルドした結果を GitHub Pages で公開する際にデプロイ用の公開鍵/秘密鍵が必要になります。

これらは、

ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""

を実行することで作成することができます。

作成される gh-pages.pub が公開鍵で、gh-pages が秘密鍵です。

GitHub の自分の scratch3_extension_template プロジェクトの Settings > Deploy keys を選び、Add deploy key ボタンをクリックし、以下のように公開鍵 gh-pages.pub の内容を貼り付けます。Title は任意ですが、github actions としておきます。Allow write access のチェックボックスにはチェックを入れます。

次に Settings > Secrets を選び、New repository secret ボタンをクリックし、秘密鍵 gh-pages の内容を貼り付けます。Name は ACTIONS_DEPLOY_KEY (必須) とします。

また、scratch_extension_template の下で、

% git co -b gh-pages
% git push origin gh-pages

を実行して gh-pages ブランチを作っておいた上で、Settings の画面の下の方、GitHub Pages のセクションで、以下のように gh-pages ブランチをソースにしてページをビルドするように設定します。

サンプルの拡張機能付きのオリジナルのScratch3を公開するための手順としては以上になります。

ファイルを何か修正して git commit & git push すれば GitHub Actions が実行されます。

試しに、install.sh の 7行目

COLLABORATOR=champierre

の champierre の部分を自分の名前、あるいは GitHub のアカウント名に変えてみてから git commit、git push してみましょう。

GitHub の Actions タブを選ぶと、git push をトリガーとして実行される GitHub Actions のステータスを見ることができます。

上記のようにすべてのステップが完了してチェックマークがつけばデプロイ完了です。

https://<自分のGitHubアカウント名>.github.io/scratch3_extension_template/

にアクセスするとカスタマイズされた Scratch3 を開くことができます。

「拡張機能を選ぶ」画面にはオリジナルの拡張機能である Hello という拡張機能を選ぶことができ、その「協力」のところにはさきほど書き換えた自分の名前もしくは GitHub アカウント名が表示されるはずです。

Hello 拡張機能を選んで追加すると、任意の文字列のアラートを表示するオリジナルブロックを使えるようになります。

GitHub Actions でおこなっていること

GitHub Actions で具体的には何をおこなっているかをみてみます。

GitHub Actions で自動実行するタスクは以下の .github/workflows/deploy.yml で定義しています。

name: Deploy
on:
  push:  -- (1)
    branches:
      - master
    tags:
      - "!*"

jobs:
  build-deploy:
    runs-on: ubuntu-latest  -- (2)
    steps:
      - uses: actions/checkout@v2  -- (3)
        with:
          repository: LLK/scratch-gui
          ref: develop
      - run: npm install  -- (4)
      - uses: actions/checkout@v2  -- (5)
        with:
          path: ./hello
      - run: sh ./hello/install.sh  -- (6)
      - run: npm run build  -- (7)
      - uses: peaceiris/actions-gh-pages@v3  -- (8)
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          publish_dir: ./build

(1) では、タスクが実行される条件を定義しています。ここでは master ブランチに git push されたときに実行されるよう定義しており、タグへの push はすべて無視するようにしています。

(2) はタスクを実行する環境です。ubuntu-latest は ubuntu の最新版を意味します。

(3) 以降では実行するタスク内容を順に定義しています。(3) では、Scratch のソースコードである https://github.com/LLK/scratch-gui の develop ブランチを checkout しています。

次に (4) で npm install を実行し、必要なライブラリなどをインストールします。

(5) では、自分自身、つまり https://github.com/<あなたのGitHubアカウント>/scratch3_extension_template を hello というディレクトリに checkout しています。

(6) で、scratch_extension_template 以下の install.sh を実行します。install.sh は scratch_extension_template 以下の各ファイルを Scratch のソースコードの適切な場所に配置したり、ソースコードの一部を変更するシェルスクリプトです。

(7) で npm run build を実行することでカスタマイズされたオリジナルの Scratch をビルドしています。

最後に (8) で、https://github.com/peaceiris/actions-gh-pages で公開されている GitHub Pages にデプロイできる GitHub Action を使ってビルドした Scratch をデプロイしています。このように他の開発者が作った GitHub Action を利用することができます。publish_dir: ./build で、デプロイするフォルダとして build を定義しています。デプロイに必要となる秘密鍵に Settings > Secrets で設定した ACTIONS_DEPLOY_KEY を使用しています。

オリジナルの拡張機能を作ってみよう

テンプレートの各フォルダやファイルを修正することでオリジナルの拡張機能を追加できる Scratch をビルドすることができます。

テンプレートでは hello となっている拡張機能名やフォルダ名をオリジナルのものに変更します。

以下の install.sh の冒頭の部分も変更します。

EXTENSION_NAME=Hello
EXTENSION_ID=hello
COLLABORATOR=champierre
EXTENSION_DESCRIPTION="Scratch Extension Template"

scratch-gui/src/lib/libraries/extensions/<拡張機能の名前>/ 以下の画像を替えることで、拡張機能一覧に表示されるバナー画像、アイコン画像を差し替えることができます。差し替えるときは同じサイズの画像と差し替えてください。

scratch-vm/src/extensions/scratch3_hello/index.js は拡張機能本体のファイルです。これを変更することで、オリジナルの拡張機能を作ることができます。拡張機能の作り方については、Sctatch 日本語 Wiki ページの「Scratch 3.0の拡張機能を作ってみよう」や、 「Scratch を改造しよう - 大人のためのScratch」などを参照してください。

英語力を手軽に測ることができる CASEC をアラフィフになって受けた話

Maker Faire、特に海外での開催のときにいつもお世話になっている高須さんのFacebookでの投稿かなにかで知った1回4000円弱、40分ほどで英語力を判定できるCASECを先日受験してみました。クレジットカードでチケットを買って自宅からすぐ受けられて便利です。

CASECについて詳しくは高須さんが書いたブログを参照してください。

» ギークのかなり偏った英語学習の話

問題に正解したかどうかで、次の問題の難易度が変わるようになっているらしく、だから短時間で終わるようになっています。もしTOEICや英検を受けた場合はこのくらいですよ、というのも示してくれるので自分の英語力が伸びているか落ちているかがわかります。忙しい人にはうってつけだし、TOEICとか受ける人も、何度も受けると受験料もバカにならないのでこれで英語力を計っておいて、目標とする点数に届きそうだなってレベルになったら受けにいくという使い方もできると思います。

僕の結果は 827/1000、TOEICスコア目安910とのことでした。

学術文献や専門的な発表の大意までは把握できるけれど、詳細を理解するところまでは達していないレベル、ということでほぼ自分の現状を表していてなかなか正確に判定できているんじゃないかと思います。問題をやってて思ったのが、会話文でちょっとしゃれた表現がでてくると、あれれ?聞いたことないな、ってなってしまっていたので、もっと Netflix のドラマや映画を英語で観たり、技術書とかでなく小説を英語で読んだりしないといけないのだろうなあ、と思いました。

2000-2004年までカリフォルニアで生活し、現地の会社に勤めていた割には点数が低いとも言えますし、それからブランクが15年以上あって、まあまあ良くキープしているなとも言えるといったあたりが自己評価です。

若干釣り気味のタイトルですが今年49歳になる身で受けてみて痛感したのは、テストを受けるには体力が必要だということです。40分で終わるのでギリギリもった感じですが、後半に向けてみるみる集中力が落ちていると感じました。ヒアリングは、確かに聴き取れてはいるのだけれど、ちょっと長い文章だと前半何言ってうたか忘れてしまいそうになっていました。また小さな文字が読み取りにくく疲れるという思わぬハンデもあります。年齢のせいにすることは好きではないし、何歳になっても新しく何かを学ぶことはできるとは思っていますが、テストを受ける場合はこうしたハンデがあることを知った上で、若いうちに伸ばせるだけ伸ばしておいたほうがいいと思います。

これは何でもそうだと思うのですが、何かに上達していく過程で、最初の初心者の時期というのは大抵つまらないものです。ゴルフを覚えたてのときは、ボールにろくにクラブが当たらず、当たりはじめてもまっすぐ遠くに飛んでいくことはほとんどないし、テニスにしても、まともに当たることはありません。それと同じように英語を勉強し始めのときには、登場するほとんどの英単語は初見で、毎回辞書を調べながら読み進めていくのは面倒で苦痛だったことを記憶しています。そんなとき、高校生のときだったと思うのですが、スティーブン・キングの小説(日本語に訳されているもの)を読むのが好きで、ある日友人に誘われ紀伊國屋書店の洋書のコーナーに行ったときのことだったと思うのですが、その頃まだ日本語に未翻訳のスティーブン・キングの原書を手に取り、英語ができればこれを読めるのだと知ってワクワクしたのを覚えている。結局スティーブン・キングの小説はちょっとレベルが高くてほんの数ページで断念したと思うのですが、代わりに当時映画化されていたミヒャエル・エンデの「The NeverEnding Story」を 辞書を引き引き最後まで読み終えることができました。今調べたら原書はドイツ語ということですが、当時は英語が原書と思い込んでいたので、このことは大きな成功体験で、その後は、英語を勉強することは苦痛ではなく楽しみとなったように思います。今にして思えば、同時にこうしたモチベーションを作り出すことが学ぶことのコツだということを自分のなかで発見できたのかもしれません。

Hotwire でモダンなウェブアプリケーションを作る

はじめに

JSON でなく HTML を送ることでモダンなウェブアプリケーションを開発できる Hotwire に少し前から興味がありました。

Hotwireとは何なのか? を始めとした日本語の情報を拾い読みしていたのですが、

ユーザにとっても、開発の進め方も Progressive Enhancement にできること

と書かれていたりして、わかったような、わからないような。

で、Hotwire を開発した DHH 氏みずから Hotwire の使い所を解説していた Podcast があって聞いてみたら、とてもわかりやすくて納得した。

» 151: DHH – Building HEY with Hotwire | Full Stack Radio

実際にデモアプリを作りながら、Hotwire がどういうものなのかを見ていきます。

クラシックなウェブアプリを作る

まずは現時点で最新の Ruby 3.0.0 & Rails 6.1.0 の環境を用意します。長らく gem install rails で最新の rails をインストールしてから rails new する方法をとってきたのですが、今回Ruby 歴 10 年の私が【絶対に】 gem install rails コマンドを実行しない理由を参考に、bundle init で作った Gemfile に rails のバージョンを指定するやり方で用意しました。

% npm install --global yarn (yarn が未インストールの場合)
% mkdir hotwire_sample
% cd hotwire_sample
% rbenv install 3.0.0 (definition not found と出た場合はその前に brew update && brew upgrade ruby-build)
% bundle init

Gemfile を開き

gem 'rails', '~> 6.1.0'

を追記したら、

% bundle install --path=vendor/bundle
% bundle exec rails new .

で rails をインストールします。

% bundle exec rails s

で rails サーバーを起動し、http://localhost:3000/ にアクセスして、「Yay! You’re on Rails!」のページが表示されればOK。

Rails でデモアプリと言えば、かつてDHHが15分でつくってみせたブログアプリです。

% bundle exec rails g scaffold Post title:string body:text
% bundle exec rails db:migrate

2005年には15分かかっていましたが、いまや一瞬でタイトルと本文が入力できるブログアプリが完成しました。

Hotwire をインストール

% bundle add hotwire-rails
% bundle exec rails hotwire:install 

で hotwire をインストールします。

次に app/views/layouts/application.html.erb の head 内に

<%= turbo_include_tags %>

を追加します。

モダン化その1 - 画面遷移しないようにする

Scaffold だけでつくったブログ、僕はこれで十分便利と思うのですけれど、2021年では「いちいち画面遷移するなんてダサい、モダンじゃない」という声が聞こえてきそうです。

そこで記事を追加する New Post のページを、画面遷移せずリスト画面の下の方にそのまま表示するようにします。

iframe で New Post をはめこむようなイメージです。実際には Hotwire の Turbo Frames という機能の turbo_frame_tag を使います。

app/views/posts/index.html.erb の New Post のリンクを

<%= turbo_frame_tag 'new_post', src: new_post_path, target: :_top %>

に入れ替え、app/views/posts/new.html.erb の中身全体を turbo_frame_tag 'new_post' で囲んでしまいます。

<%= turbo_frame_tag 'new_post' do %>
  <h1>New Post</h1>

  <%= render 'form', post: @post %>

  <%= link_to 'Back', posts_path %>
<% end %>

turbo_frame_tag に続く 'new_post' の部分にはユニークなIDを付け、同じIDの部分が対応しています。つまり、index.html.erb の turbo_frame_tag 'new_post' の部分に、app/views/posts/new.html.erb の中身全体がそのまま表示されます。

app/controllers/posts_controller.rb の def create 内で posts コントローラーの show にリダイレクトしている部分はコメントアウトし、index にリダイレクトするように変更します。

        # format.html { redirect_to @post, notice: 'Post was successfully created.' }
        format.html { redirect_to posts_url, notice: 'Post was successfully created.' }

これで、画面遷移せず、記事を登録できるモダンなアプリの出来上がりです。この変更には、リダイレクト先を変える以外、モデルやコントローラーの修正はほとんど必要ありません。

モダン化その2 - リアルタイムでページを更新する

ブログのアプリでその必然性はほとんどないと思うのですが、モダンなアプリというのは、データが変更されたときにリロードすることなくリアルタイムにページが更新されるものです。

2000年-2004年アメリカに住んでいたとき、サッカー日本代表チームの試合の模様をテキストで伝える実況サイトを見ていて、5秒間ごとにリロードボタンを押しながら一喜一憂していたものですが、2021年ではもうそんなことは許されません。

そこで Hotwire の Turbo Streams を使います。

これを使うには redis が必要なので

% brew install redis

で redis をインストールします。

記事が更新されたときに、更新されたことを通知するため、app/models/post.rb の Post モデルに broadcasts_to を追記します。通知先となる 'posts' は、後述する に付ける id です。

class Post < ApplicationRecord
  broadcasts_to ->(_post) { 'posts' }
end

Turbo Streams を使ってデータをリアルタイムに更新するには更新する部分を partial ファイルに切り出す必要があります。ブログ記事を列挙している部分を partial ファイルに置き換えます。

新たに app/views/posts/_post.html.erb というファイルを追加し、index.html.erb から tr 部分を移してきます。id には dom_id(post) を設定します。

<tr id="<%= dom_id(post) %>">
  <td><%= post.title %></td>
  <td><%= post.body %></td>
  <td><%= link_to 'Show', post %></td>
  <td><%= link_to 'Edit', edit_post_path(post) %></td>
  <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>

tr 部分を parial ファイルに移したので、app/views/posts/index.html.erb の tbody の部分は以下のようになります。

  <%= turbo_stream_from 'posts' %>
  <tbody id="posts">
    <% @posts.each do |post| %>
      <%= render post %>
    <% end %>
  </tbody>

turbo_stream_from 'posts' で post インスタンスに関する変更内容を WebSocket 経由で受け取れるようになります。

以上でモダン化その2が完了です。

一方のブラウザで記事の内容を更新したり、削除すると、もう一方のブラウザにすぐに反映されます。

まとめ

ここまでで、クラシックなアプリに 1) 遷移しない画面、2) リアルタイムに更新される画面、というモダンなウェブアプリケーションに特徴的な機能を追加しました。

意図的ですが、JavaScript は一行も書いていません。しかしさらに細かくモダンな機能を追加していくにはさすがに JavaScript を書く必要があり、そこは Hotwire の Stimulus を使うことになります。Stimulus の導入は若干大変なので、次の機会にでもブログで紹介しようと思います。

Hotwire を少し使ってみてわかったのは、サーバーサイドでHTMLをレンダリングするクラシックなウェブアプリケーションをベースにして、必要な箇所に Turbo Frames を使って遷移を省いたり、Turbo Streams を使ってリアルタイム性を追加したり、細かな機能追加には Stimulus を使っていくというのが Hotwire のアプローチだということでした。

冒頭の Progressive Enhancement (段階的な改善)というのは、そういうことなのだ納得しました。

Podcast での DHH 氏の解説の端々に、「Ruby 最高、全部 Ruby で書きたい。JavaScript はできるだけ書きたくない」という Ruby 愛が感じられます。2019年、東京で開催された Rails Developer Meetups の基調講演 で、DHH は、ほとんどのアプリケーションは JavaScript のフレームワークなんて必要としていない、もっとシンプルに作れるはずだと語っていました。

デモのために無理やり Turbo Streams を使ったブログ記事の更新のように、そのままで十分なところは通常のサーバーサイドレンダリングのままに、たとえばチャットの画面のように必要なところだけをモダン化していけば良いというのが DHH および Hotwire のアプローチなのだと思います。

参考

さらに詳細を知りたい場合は、Hotwire | Drifting Ruby のチュートリアル動画に沿って、チケット管理システムのデモアプリを作ってみることから始めるのが良いと思います。

ファーレ立川アート、街なかに100以上パブリックアート...

ファーレ立川アート、街なかに100以上パブリックアートが展示されておりクエストの作りがいがありました。

https://t.co/WkdnKlCTSD

まだわずか3つだけですが、結構良問が作れたと思うので機会があったらやってみてください。

まちクエストで「静岡ナンバー」のクエストをしかけました...

まちクエストで「静岡ナンバー」のクエストをしかけました!みつけてみてください https://t.co/JkdiQI4zII #machiquest

プロフィール

株式会社まちクエスト代表、つくる社LLC代表。

Scratchで楽しく学ぶ アート&サイエンスRaspberry Piではじめる どきどきプログラミングを書きました。

オンラインコンテンツ: 大人のためのScratch

Amazonから図書館検索 Libron、iPhoneアプリ ひらがなゲーム かなぶん を作っています。

Email: webmaster at champierre dot com

Twitter @jishiha

最近のエントリー

アーカイブ