ここは Web や Android アプリのプログラマでありチェスやバイク、株式投資を趣味とするコジオンこと Hideyuki Kojima の日記です。 毎日何かしら欠かさず書いています。 この Blog の他に Qiita にもいくつか技術系の記事を投稿しています。 YouTube のチェス実況チャンネル に毎日 lichessChess.com の 10 分レート戦の実況動画を投稿しています。 連絡はメールでお願いします。kojionilk あっとまーく gmail どっと com です。

南池袋 PA の自動販売機のたこ焼き

ゴールデンウィーク前半の最終日なので一般の高速道路が混んでいる代わりに首都高が空いていると判断した。 今日は首都高を 140 km ほど走ったが、読み通り渋滞に 1 回もはまらなかった。 何より C1 (都心環状線) でも全く詰まらないのは快適だ。

今日は昼時に昼を食べずに行ったので、やっと念願の「自動販売機のよく分からない軽食」を注文することができた。 今回はたこ焼きとしたが、グーテンバーガー (昔あったハンバーガーの自動販売機) のように飾り気のないパッケージで出てくるのかと思いきやちゃんとした箱ででてきたのが予想外だった。 ちなみに電子レンジ調理の時間 (120 秒) 待たされる。 食べてみたが、タコがものすごく小さい。 最初入っていないのかと思った。 そして当然だが電子レンジ調理特有の感じがした。 あまりにチープなのだが、やはり首都高で食べると趣が違う。

その後芝浦 PA で別の惣菜パンでも食べようと思ったらちょうど小銭と千円札を切らしてしまっていて食べることができなかった……。

埼玉県道 361 号線で放牧されていた

埼玉県道 361 号線の放牧

1 ヶ月に 1 回は秩父に行っている気がする。 やはり埼玉県住まいなので懇意にしたい地域なのは間違いない。 いつも通り埼玉県道 361 号線で秩父入り。 この Blog で何度となく書いているが、この県道は展望は良いし牧場や天空のポピーなどの見どころも多いしでとても気に入っている。 ただ天空のポピーがある方は厳密には 361 号線ではなく牧場を北に抜けたほうが本道のようだが、そちらの方面は展望が悪くあまり面白くない。

今日通ったら天空のポピーに近い方に牛が多数放牧されていた。 こちらも牧場の敷地内なのだろう。 私の他にも多くのバイクや車が足を止め、この放牧を見て楽しんでいた。 天気もいいし素晴らしい景色だった。 まだポピーが咲くには早い時期だったので一面大草原といった感じだったが、あと 1 ヶ月もすれば美しい花が咲き乱れるのだろう。

久々の埼玉県道 367 号線

埼玉県道 367 号線

1 年半前に行ったのだが久しぶりの来訪。 途中幅員が狭くなり車だと大分厳しい道が続くが、秩父らしい景色が堪能できる。 写真は埼玉県道 367 号線に入ってちょっと行ったところにあるいかにも開発したといった感じの山々の風景。 足を止めてこういう風景を眺めるのも楽しい。

今回はあくまで道を楽しむのが目的なので、丸神の滝まで走っていってそのまま U ターンした。 奥には林道があるが、今日は特に虫が多くちょっと停車しているだけで虫が寄ってくる問題に悩まされた。 ラーツーをやろうと思ったら適切な場所が見つからないだろう。

ちなみにここまでの道はゴールデンウィークにもかかわらず車がほとんど通っていなかった。 やはり観光価値のない道路は時期関係なく空いていて快適だ。 こんなに素晴らしい景色が待っているのに、いわゆるお決まりの観光地ばかり行っているのはつまらない。 ライダーならば尚更だろう。

十数年ぶりのフライングガーデン

フライングガーデン

夜はフライングガーデンに爆弾ハンバーグを食べに行った。 私は外食でハンバーガーは日常のように食べるがハンバーグもしくはステーキは年 1, 2 回ほどしか食べない。 従ってフライングガーデンのようなハンバーグ中心の店に足がむくことはほぼ無く、気がつけば十数年前に行ったきり全く行っていなかった。

安いグルメバーガーにあるような合いびき肉ではなくちゃんとした牛 100 % のハンバーグ (メニューにも書いてあった) で食べごたえがあった。 キングサイズ (250 g) でも単品ならば 1,000 円切るのはなかなか素晴らしい。 美味しいのだが、やはり私の好みからするとハンバーガーには到底敵わないといった印象だった。 ハンバーグにパンを付け合わせて食べても確かに美味しいのだが何か違う感じだ。

子どもに次の誕生日プレゼントに 2DS が欲しいとお願いされたが、年齢の割に視力が悪くなり過ぎているのがいつも気になっていた。 スマホで動画を見ている時があるが目からの距離がとても近い。 いつも注意しているが、言った直後はやってもすぐに忘れてしまい元に戻ってしまう。 そんなに重大なことだとは思っていないのだろう。 「悪くなっても眼鏡をかければいいだろう」ということなのかもしれない。

もう少し真剣に考えてもらういい機会だと感じたので、「自分が君のことを 1 ヶ月間観察しているので、その間ちゃんと目からの距離を確保できていたら考えてもいい」と約束した。 ちなみに自分も目からの距離を測ってみたが、さすがに目を使う仕事をしていて眼精疲労にも悩まされる場面もあるからか普段から気をつけているので 30 cm 以上 (40 cm くらい) 距離をとっていた。 それに加え目からの距離を確保するためになるべく PC やタブレットを使うようにしている。 ただ、電車の中で他人を観察していると大人でも目からの距離が近すぎる (30 cm どころか 20 cm も離れていない) ような人もちらほら見かける。 電車の乗り換えでホームを歩いているとスマホをいじっていない人を探すのが難しいくらいだし、「スマホ老眼」などと呼ばれる時代になってきたのも分かる気がする。 ただ、大人になって視力が落ち着いてからならまだマシなのかもしれないが、子どもの場合ダイレクトに目が悪くなっていくので注意しないとどんどん近視が進んでしまうだろう。 本当に気をつけてもらわなければ私としても気が気でないところだ。

数日間 Markdown を快適に書ける環境をいろいろ探していたが、結局 MarkdownエディタはTyporaとJotterPadで決まりだ! を見て同様に Typora と JotterPad を使用することにした。 Boostnote も良かったのだが Android 版の出来が悪いのが致命的だった。 現状 Dropbox としか連携できないし Dropbox 側の決まったディレクトリから移動することができない。 また保存フォーマットが Markdown テキストでなく JSON ライクな形式になっているのも微妙なところで、いくら Markdown 形式でエクスポートできるといっても気軽に見ようとした時に JotterPad が入っていないと見ることができない。 素の Markdown テキストならばどんな環境でも見ることができるので個人的にはそちらの方が嬉しいところだ。 また Boostnote はフォルダ分けに加えてタグ付けもできるが、個人的には管理が面倒なのでタグは不要だ。

Typora だとフォルダ分けしてそのアウトライン (エクスプローラのような階層化されたフォルダの状態) を左サイドバーで見ることができるし Markdown を書くとその場で整形されて表示される為プレビュー画面が分かれていないのが新しいと思った。 Redmine のように Textile で書かなければならない時に Textile 形式のエクスポートがとても便利だ。

歳のせいか分からないが、「あれ、これなんだっけ」といった事が増えてきたように感じる。 忘れそうなことは逐次 Markdown でメモ書きを残す癖をつけたいところだ。 この Blog も自分の備忘にとても役に立っている。

そろそろ暖かくなってきたので半袖の季節か、ということで今日から半袖シャツということにした。 まだ若干肌寒いのだが、それよりも長袖を着て電車の中で暑くて汗をかくのが嫌いなのでこれでいいことにする。 あと 1 ヶ月ちょっとでなるべくバイクに乗っておかないとその後暑くて乗りにくくなってしまうのだが 5 月は他のイベントも多いのが困りどころではある……。

折り畳み傘の中では最大級にコンパクト

Knirps X1

私が 3 年以上使用した Knirps (クニルプス) の T2 がさすがにボロボロになってきたので今回買い換えた。 T2 は長さが 27 cm あるので私がいつも通勤で使用している小さめのショルダーバッグには少し斜めにすれば入るような形だ。 大きいビジネスバッグを持ち歩いている方は問題ないサイズとは言えるが、私のように少しでも荷物をコンパクトにしたいタイプの人にはちょっとだけ大きい。 個人的には長さ 26 cm 以内、できれば 24 cm 以内に抑えたいところだがそのサイズで適切なものがなかなか見つからない。

今回は同じ Knirps の X1 という定番モデルを購入 (写真上。下は T2)。 これは長さが 18 cm というとてもコンパクトなモデルで、ショルダーバッグに余裕で入るばかりかレッグバッグにも入るし、ボディバッグにだって簡単に入るだろう。 これで使い勝手が最高だったらまさにベストな折り畳み傘ということになるので、試しに使う機会を待ち望んでいた。 ようやく今日その機会を得たので、ここにレビューとして書き留めておく。

十分許容範囲内の使い勝手

この X1 には携帯用のハードケースが付属し、電車に乗っているときなどはそのケースにしまってバッグに放り込んでおけばバッグ内部を濡らさずに移動することができるはずだ。 その操作が「自然にできるか」というのが最大の関心事だった。 つまり駅に着いた時に折り畳み傘をうまく畳み、ハードケースに格納してバッグに放り込む、までの操作で立ち止まってずっともたついてしまうようでは使い勝手がいいとは言えない。 事前に自宅で傘を開いてハードケースにしまうという操作の練習を行ったが、ハードケースがかなりピッタリ目のサイズで作られているので正直「大丈夫か?」という感じだった。

今日試してみたところ、確かに T2 よりはもたつくが十分許容範囲内だった。 傘を畳む時も手動なのだが、割と雑な感じでグルグル巻いてもちゃんと折り畳めるし、ハードケースに収めるのもそんなにギッチリ調整しなくても入れることができた。 このバランス感覚は素晴らしいと思う。 また、取っ手が短くて若干持ちにくいが、これも許容範囲内 (別に普通に使える) という印象だった。 何よりこのコンパクトなサイズを実現できているのだから、少々使い勝手に影響してしまうのは仕方がない。

あとこの傘で魅力的なのはデザインや質感だ。 この X1 というモデルは多種多様なグラフィックが用意されているし、なかなか高級感がある。 私は自分が使えそうな範囲内で一番派手なものにしたが、なかなかおしゃれで気に入っている。

一番気になったのはサイズに対して割と重量がある (ケース込みで 285 g) ことだ。 検討対象としたのがモンベルのトレッキングアンブレラだが、こちらは 128 g と異常に軽い。 毎日持ち歩くものということでなるべく重量を軽くしたかったところだが、やはり他の利点もあるので自分はこの選択で間違っていなかったように思った。

らーめん春樹に行ってきた。 ここはつけ麺の麺を 900 g まで増量しても普通盛 (350 g) と同じ値段で食べることができる。 ちなみに 350 g, 550 g, 750 g, 900 g の順で普通盛、大盛、特盛、山盛となっている。 大盛でも 550 g という表示を見てちょっと躊躇ってしまったが、このグラム数は茹でた後の重量であって茹でる前のそれとは異なることを後で知った (うどん等は乾麺 200 g でもそこそこ多い印象)。 私は大盛を食べたのだが、確かに多かったがまだちょっといけそうな気がした。 バトルを繰り広げたいのならば特盛が良かったように思った。

しかしつけ麺のスープ自体は至って普通で特段美味しいとも思わなかったので、恐らくそんなに行く機会は無いだろう。

MediaPad M5

MediaPad M5 の日本国内発売をずっと待ち望んでいるのだが、事前情報は上四半期に発売されるだろうということぐらいで未だに続報はない。 MediaPad M3 Lite 10 でもまだまだ戦えてはいるのだがやはり状況によってフリーズしたりとずっとタブレットばかり触っている私にとっては物足りない端末になってきたのも事実だ。 P10lite の時もそうだったがファーウェイ端末の Lite を冠するものはハードに使う予定の場合は止めたほうが良さそうだ。 海外通販 (アリエクスプレスなど) に MediaPad M5 が並んでいるが、あまりに高いので何とか思い留まっている。 MediaPad M5 に関しては去年から待っている。 欲しいものをこれだけ待ったのは久しぶりだ。

HEROZ アプリコンプリート

そういえば最近はチェスに加えてバックギャモン (バックギャモンエース) を始め、そして「どうぶつしょうぎウォーズ」や囲碁ウォーズまでダウンロードしてプレイを始めた。 これで HEROZ アプリをコンプリートしてしまった。 しかし、プレイするテーブルゲームが 6 個 (将棋、どうぶつしょうぎ、麻雀、チェス、バックギャモン、囲碁) もあると広く浅くになってしまうな、というのが最近の悩みどころだ。 とりあえずチェスとバックギャモンは面白いし、麻雀は最近四麻が念願の雀狼になれたので割と熱心にプレイしている。 どうぶつしょうぎは単純な上に短時間でスパッと終わるので細切れの時間にちょっとプレイするのに便利だ。

囲碁だけはどう打っていいのか良くわからない。 これも本を買って体系的に勉強しないと強くなれないだろうか。 今はバックギャモンブックを購入してバックギャモンの勉強をしている……。

取手市のグルメバーガーショップ

BIG SMILE エッグチーズバーガー

今日は久々にグルメバーガーを食べる為にバイクで出かけた。 今回は取手 (とりで) 市にある BIG SMILE を目指した。 関係ないが取手市がいつも茨城県なのか千葉県なのか分からずに曖昧になっていた。 取手市は茨城県。 今度こそ覚えた。

このグルメバーガーショップは割と何にもないところにあるのでバイクで行っても余裕で停めることができる。 しかし入ってみると店内はいかにもな個人店といった感じでこぢんまりとしていて、意外なことに既にお客さんでいっぱいになっていた。 私はカウンターの隅に案内されたが、正直もうスペースがない。 ハンバーガーはプレーンなハンバーガーから 4 種類のソースを選び、更に適宜トッピングをしていくスタイル。 私はスイートチリソースを選択し、トッピングとしてエッグとチーズということにした。 プレーンなハンバーガーが 750 円と安く、これだけトッピングしても 900 円という安さ。 やはり郊外店はお値打ちだ。

出されたハンバーガーを食べて新しいなと思ったのが、今まで食べた中で一番バンズが固いタイプだったということだ。 まるでフランスパンのように固い。 グルメバーガーを食べる時のコツとして口で挟めるようにバンズを上下から押してコンパクトにするというのがあるが、このバンズは固いのでなかなかうまくいかない。 とはいえこれはこれで美味しい。 パティもゴツゴツした食べごたえのあるタイプで好印象だった。 ちなみにポテトはフライドポテトではくハッシュドポテトだった。

BOLT 30,000 km 達成

BOLT 30,000 km

BIG SMILE からの帰り道で無事に総走行距離 30,000 km を達成することができた。 20,000 km 達成が去年の 6 月 25 日なので約 10 ヶ月で 10,000 km 走破したといったところだ。 平均で月 1,000 km 走っているということで、まずまずのペースを維持できているといったところか。 このペースだと総走行距離 10 万 km 達成するまであと 6 年かかる。 まだまだ楽しめそうだ。

ところで今日はとても暑く、メッシュジャケットに半袖 T シャツという出で立ちでも暑くて仕方がなかった。 去年の 8 月は涼しい日が多くてメッシュジャケットで快適に走れたと思うのだが、今日はそれより明らかに暑い。

毎週ツーリングに出ているようだとお金がかかって仕方がないので、今日は街乗りにした。 かなり暑くなるということでメッシュジャケットで出かけたが、それでも日中はちょっと暑かった。 4 月下旬からこれだと先が思いやられる……。

3 週間前キリンビール仙台工場に見学に行ったということで、今日は一番搾り (大瓶) を購入。 やはり製造工程の説明を受けたり試飲したりするとそれが強く印象に残って、ビール売り場に行くとつい見てしまう。 キリンビール仙台工場の見学は無料だったので、これが「損して得取れ」なのか、とちょっと思った。

プログラマに特化した Markdown 記法のメモアプリ

昨日の記事で主に他人に対して Markdown でドキュメントを起こす手段は MkDocs がいいだろうという事になったわけだが、自分用のメモアプリでも Markdown を使いたいという欲求が出てきた。 Markdown でメモを残しておけば、もしアプリを乗り換えたい場合でも乗り換え先が Markdown 記法をサポートしていれば比較的簡単にデータを移すことが可能だ。

ちなみに今までは Google Keep を使用していた。 Evernote を使用していた時期もあったがゴチャゴチャしていて好みではなくすぐに止めた。 Google Keep はとてもシンプルなメモアプリで簡単に PC / Android 間でクラウド同期できるし、チェックボックスを使用して TODO リストを作ることも容易なので残作業の管理にとても役立っていた。 ただ、以下の点で少しだけ不満に感じていたのは事実だ:

  • 記法がプレーンテキストの為若干物足りない
  • フォルダ分けの機能がない (タグを使用して近い事はできるが)

MkDocs の情報を探すついでにクライアントアプリで代わりになるようなものを探していたら Boostnote がヒットした。

凄くいいなと思ったところ

  • Markdown 記法をサポートしているが表組とチェックリストに対応していること
  • Boostnote で生成される結果がただのファイルなので簡単に Dropbox などのクラウドストレージと連携できること
  • フォルダ分けに対応していること (タグにも対応している)
  • Vim キーバインドが使えること
  • Windows / Mac / Linux アプリだけでなく Android / iOS アプリが用意されていること

尚 Boostnote がサポートしている Markdown 記法に関しては公式の Blog 記事「仕事効率、学習効率を加速させるMarkdown記法の紹介」に詳しく書かれている。 テーブル記法もチェックリストも Qiita の Markdown 記法と同じとなっているのがありがたい (MkDocs も同じだが)。

このアプリを見ると真っ先に思い出すのは Qiita と連携できる同じく Markdown 記法のメモアプリ Kobito だが、今日見たら既に公開終了していた……。

現在 Android 版の出来がかなりイマイチ

Android 版も Google Play から落として使ってみたのだが、機能が全然足りていないし挙動が不安定でよく落ちる状態で正直イマイチだと思ってしまった。 記事を参照しようとすると編集画面になってしまってプレビューを表示したい場合は一旦保存しなければならない UI など全く練り込まれていない感じがする。 また Dropbox 連携ができる (Google Drive 非対応) 為試しに連携して使おうとしたところ記事を追加しようとすると 100 % クラッシュするという不具合に見舞われた。 にっちもさっちもいかないので Dropbox に作成された boostnote-mobile フォルダを Windows 側のアプリで Add Storage Location した。 そうしたら 1 つもフォルダが無い状態だったので 1 つ追加した上で Android アプリ側で記事を追加しようとしたら今度はうまくいった。

正直今のところ Android 側でちょっと見たいくらいの要件にしか使えない。 今後の更新に期待したいところだ。

久々に使い勝手の良さに感動した

ドキュメンテーションツールといえば Sphinx が定番だったし過去私もよく使用していたのだが Sphinx が標準でサポートしているマークアップ言語である reStructuredText がお世辞にも書きやすいとは言えず常に記法を調べたり別の箇所からコピペするなどして頑張って書いていた記憶がある。 最近では技術系に関わらず文書を起こす場合は Markdown がデファクトの地位を確立していると思う。 Sphinx でも設定すれば Markdown を使えるようだがこれも使い勝手がいいとはいえない。 設定が簡単で Markdown を書くことに集中できるようなツールを探していたのだが、今回見つけた MkDocs がとても素晴らしかったので自分用メモを兼ねてここに書き記しておく。

  • Markdown なのに最初から表組みの拡張が入っている (しかも書きやすい)
  • mkdocs serve でローカルサーバが起動するが Markdown ドキュメントを編集すると即時反映される (ビルド不要)
  • Sphinx の admonition だったり footnotes (注釈) が設定 1 行付け足すだけで簡単に使用できるようになるし他の機能も必要に応じて 1 行書くだけで即追加可能

導入・設定

導入に関してはカンタンにドキュメントが作れるmkdocsをはじめてみようがとても分かりやすかった。 基本は Python をインストールして pip からすべて導入できるので楽だ。 細かい設定に関しては MkDocsによるドキュメント作成が詳しい。

その他の設定は MkDocs 公式 (英語) を確認すればよい。 また使用するテーマに関してだが readthedocs か material のどちらかがいいということだが私は material にした。 CSS をいじらずに配色 (マテリアルデザインにおけるテーマ色とアクセント色) が簡単に変更できるし、日本語対応がされている。 後何故か readthedocs の方だとコードハイライト時にうまく表示されなかった。

私が導入した設定

site_name: 'サイトネーム'
site_description: 'サイト説明'
site_author: '所有者名'
site_url: 'サイト URL'
copyright: '著作表記'

theme:
  name: 'material'
  language: 'ja'
  palette:
    primary: 'light blue'
    accent: 'pink'
  font:
    text: 'UD デジタル 教科書体 NK-R'
    code: 'Consolas'
    
extra:
  search:
    language: 'jp'
    
markdown_extensions:
  - admonition
  - codehilite
  - footnotes
  - pymdownx.inlinehilite
  - pymdownx.tasklist:
      custom_checkbox: true

Material for MkDocs

Material for MkDocs 公式 (英語) にすべて網羅されているので設定はここを見れば良い。 言語ロケールの選択をすると全体的に日本語表記になる (検索も日本語対応されている) し、テーマ色やフォント (コードハイライトと別々に定義できる) の設定も可能だ。

Extensions

以下を導入した:

  • Admonition (Sphinx のような警告文)
  • CodeHilite (コードハイライト)
  • Footnotes (文末注釈)
  • pymdownx.inlinehilite (インラインコードのハイライト)
  • pymdownx.tasklist (チェックリスト)

今日は「ビッグマック ベーコン」と「ビッグマック BLT」の発売日だったので早速 BLT の方を食べに行ってきた。 ビッグマックにベーコンとトマトを挟んだだけの商品のはずなのだが、結構味わいが違ってビッグマックというよりはマクドナルド期間限定メニュー特有の BLT の味がする。 ただ、ただでさえ崩れやすいビッグマックに更にものを挟んでいるということで食べている最中の崩壊がひどく、かなり手を汚してしまった。 このクラスになるとバーガー袋がないと食べにくい。

後、あくまで味わいがだいぶ違うというだけで、素のビッグマックより美味しいというわけではないと思った。 リピートはしないだろう。

範囲は広がったものの価格が大幅に上がったのがネック

先日今年の首都圏ツーリングプランの概要が発表された。 NEXCO 東日本のプレスリリースから確認できる。 要約と去年との変更点は以下の通りとなっている:

  • 去年は首都圏だけだったが今回は中京・関西・九州にもコースが用意された
  • 去年は「2,500 円 / 2 日間」固定だったのだが今回から「2,500 円以上 / 2 日間」 (福島方面のみ 3 日間) に変更された
  • 4 月 27 日開始となったので春のツーリングでも利用できるようになった

ネット上では好意的に受け取っている方も結構いるようだが、自分としては価格が上がってしまったのが最大のネックだと感じる。 ツーリングに行くたびに 1 万円近い交通費 (高速代 + ガソリン代 + エンジンオイル換算額など) を出せるような裕福な人ばかりではない。 中京・関西・九州に全く疎いので見当違いな意見かもしれないが、首都圏の価格設定だけ他の地域より値段が高めに設定されているのも解せない。 自分としてもそんなに泊りがけのツーリングばかり行くわけにはいかないし、日帰りでこれは高すぎる。 最もライダーの平均年齢は 52, 3 歳だというのをどこかで見たのでそれを考えるとこの価格設定で正しいのかもしれないが、その年齢層の方がこういう計算が面倒くさいプランをそんなに頼むのかというのも疑問に感じる。

このツーリングプランだけでは微妙に足りない部分が多く (例えば首都高を経由しなければならないなど) 実際はこの固定額以上にお金がかかるのもマイナスだ。 特に自分は埼玉在住なので神奈川方面に行くにはどうしても首都高を通らなければならず (将来的に外環道が中央道や東名高速まで接続できれば別かもしれないが……) 首都高の往復でも 1,070 × 2 = 2,140 円かかってしまう。 値段を上げるならばそれだけで全カバーできるようにして欲しかったと思うし、できれば去年のプランよりも範囲が狭くていいので値段が安いプランを併売して欲しかった。 個人的には下道がキツい区間だけでも高速でパスできれば十分な場合が多いからだ。

去年は 2,500 円固定で今回のツーリングプランより対応区間が短かったのだが、正直首都圏さえ抜けてしまえば下道でも楽しい道が多く、高速の必要性をあまり感じないのでそんなに気にならなかった。 唯一気になったのが東北道が宇都宮 IC までなのが少し短いかなと思ったくらいだったし、関越道の方は碓氷軽井沢 IC までいけるのがちょうど良かった。

以下、首都圏のコースに関する自分の感想を書く。

関越道・上信越道・中央道コース (4,000 円 / 2 日)

東部湯の丸 IC か諏訪 IC まで行ける。 確かにビーナスラインまで行きやすくはなったが 2 日間で 4,000 円は高すぎる。 更に自分の場合練馬 IC か坂戸 IC まで出ないといけないので追加料金がかかってしまう。 泊りがけでフルに高速を使う場合は使えるかなとは思うが、それ以外では使えない。

あと関越道方面が高崎 IC 止まりなのは何故なのだろうか。新潟には行くなということだろうか。

東北道・常磐道・磐越道コース (5,000 円 / 3 日)

5,000 円という価格設定に閉口するが対象期間が 3 日に増えている。 ただ、私だと 3 日間もツーリングに出られる機会はほとんどないので関係がない。 去年は宇都宮 IC までだったが今回は福島飯坂 IC まで伸びているのは魅力的。 福島の泊りがけのツーリングには使えそうだ。 逆に言うとそれ以外には使いにくい。

東関東道・館山道・常磐道コース (3,000 円 / 2 日)

これは何故か 3,000 円だがアクアラインが対象に入っているのが嬉しい。 茨城方面も日立南太田 IC やひたちなか IC まで行けるというのは十分で、一番使い勝手が良さそうなコースに見える。

首都圏 東名・中央道コース (3,000 円 / 2 日)

3,000 円で新静岡 IC まで往復できてしまうのはすごい。 神奈川県在住だと大喜びしそうなプランだ。 ただ、自分にとっては首都高を経由しなければならないので +2,140 円されてしまい結果的に 5,000 円超えになってしまうのが辛いので残念だがやや使いにくい。

「ちぇ」が気になっていた

月配列 K では「ち」を KA の2 打鍵 にしており割といい位置ということで定着しているのだが、「ちぇ」と打つ時がどうもイマイチ (KAK4) なのが気になっていた。 「チェス」「チェック」「チェーン」など割と出てくる。 ずっと我慢して打っていたのだが、最近「月配列 K で移動したシフト面に穴があるのだから、そこにこういった拗音 (厳密には外来語につく「ぁぃぇぉ」に紐づく 1 モーラは拗音ではないらしいが) をショートカットできるキーを配置してはどうか、と思いついた。 結構前から試していたのだが、それなりに定着してきたのでここに書き記してみる。

月配列 U9 を全面的に参考にした

「ちぇ」の他には「でぃ」も欲しかった特殊音拡張で、月配列 K では同様に 4 打鍵だったものが 2 打鍵になる。 月配列 K の場合 2-263 式のシフト面から「ぁぅぇぉふゅゃ」の 7 文字 (とシフトキーに配置された「らも」) を移動しているのでここに埋めることができる。 さて、何を置こうか……ということで思いついたのが月配列 U9 の特殊音拡張だ。

月配列 U9 では「ちぇ」「てぃ」「でぃ」「ふぁ」「ふぃ」「ふぇ」「ふぉ」と「ぁぃぅぇぉ」をそれぞれ 3 打で打てる特殊音として配置している。 ちなみにホームページには特殊音拡張の記載があるが DvorakJ に含まれる月配列 U9RC2 の設定ファイルにはこの定義はない。 やはり私と同じように考えて後から追加されたのだろう。 「ぁぃぅぇぉ」は既に 2 打鍵 (月配列 2-263 式と同位置) で用意されているのでちょっと何の意味があるのかよく分からなかったが、配置を固めておくことで記憶に定着しやすくしたという意味があるのかもしれない。

月配列 K ではこの「ちぇ」「てぃ」「でぃ」「ふぁ」「ふぃ」「ふぇ」「ふぉ」を丸ごと採用することにした。 それにより以下のような配列図が得られた:

[
  |ふ|ら|ゅ|ゃ|  |  |も|ー|  |  |
そ|こ|し|て|ょ|つ|ん|い|の|り|「|」
は|か|  |と|た|く|う|  |゛|き|れ|
す|け|に|な|さ|っ|る|、|。|゜|  |
]

[d],[k][
ぅ|ぁ|ぃ|ぇ|ぉ|  |  |  |  |  |  |
ふぁ|ひ|ほ|ちぇ|め|ぬ|え|み|や|ふぇ|  |  
ち|を|  |あ|よ|ま|お|  |わ|ゆ|  |
ふぃ|へ|せ|てぃ|でぃ|む|ろ|ね|・|ふぉ|  |
]

-shift[
!|@|#|$|%|’|&|*|(|)|-|=|¥|
  |  |  |  |  |  |  |  |  |;|[|]
  |  |  |  |  |  |  |  |  |  |’|~
  |  |  |  |  |  |  |,|.|?|  |
]

月配列 2-263 式の「ぃ」の位置は「ち」で埋めてしまったので、それ以外の「ぁぅぇぉ」の位置にそれぞれ「ふぁふぃふぇふぉ」を埋めた。 割と収まりがいい。 ちなみに英語キーボードでもいけるように中黒 (・) はシフト面 D. の位置に移動している。

それにしても月配列 U9 にしてもそうだがこうやってドキュメントに残してもらえるのはすごくありがたい。 月配列 U9 作者 U ジロー様のかな出現頻度表もとても参考になっている。 私もこうありたいと思うので、自分の試行錯誤を Blog に残しておくことは自分用のメモ以上の意義を感じる。

効果

既にタイプウェル国語 K 常用 XC になるまで鍛えてしまったので微妙なのかもしれない、と思っていたが「ちぇ」は明らかに楽になりよく使用するようになった。 また、「ふぁ」「ふぃ」「ふぇ」「ふぉ」が 3 打鍵から 2 打鍵になったということで 1 打鍵のみのショートカットになったのだが、やはり最上段に指を伸ばさなくて良くなったのがとても楽に感じ、気がついたら割と使うようになった。 あと「シフト文字を押下してから小指をそれぞれ上下に動かして打鍵する」という挙動 (KQ, KZ, DP, D/) が記憶に残りやすいのも影響している。

一方「てぃ」「でぃ」に関してはローマ字の THIDHI で打てるのが羨ましくて是非欲しかったのだが、出現率が低いからなのか未だに定着していない。 意識的な練習が必要なのかもしれない。 この練習に最適なのはタイプウェルのカタカナ語だろう。 ちなみに今回の調整はタイピングを速くしたいとかそういう意図は全くない。 ただ単に自分が楽をしたいからであって、その狙いは割と達成できた気がする。

私は楽天カードマンなので当然のように毎月の楽天お買い物マラソン (もしくはスーパーセール) に参加している。 普段買いたいものがでてきたら Google Keep にメモっておき、マラソン期間に入ったらすかさずまとめて買うわけだ。 今回のように 5, 0 のつく日が近くにある場合はその日になるまで待ってから +2 倍ポイントのキャンペーンにエントリーしてから買う。 おかげで毎月かなりのポイントがつく。 これはやみつきになる。

今回は冷や麦やカレーの買いだめをしてみた。 あと楽天ブックスの +1 倍を満たすためにバックギャモン・ブックを購入。 バックギャモンは HEROZ のアプリにあったので少しずつやるようにしているのだが、まだまだ戦略などよくわかっていないのでこの機会に少し勉強してみることにする。 ある程度歳をとってからコンシューマ機を含めたゲームには食指が動かなくなってしまったが、こういうボードゲームの類はまだ結構やる気になるようだ。

朝食バイキング

午前 5 時頃起きたのだが、朝食バイキングが 6 時半からだったのでそれまでゆっくりと過ごした。 朝食バイキングは前情報通りマグロの漬けが食べ放題だった。 小皿いっぱいに盛り、更にお代わりするなどして食べまくった。 これはお得だ。 ご飯も炊き込みご飯だったし、ここの朝食バイキングはかなりいい感じだった。

静岡は午後から雨の予報だったので早めに撤収する予定だったが、夕方くらいまで大丈夫そうなのでゆっくり部屋で過ごして 9 時頃ホテルを後にした。

富士山スカイラインを走って帰宅

最初富士山スカイラインから道志みち、奥多摩周遊道路など走ろうかなと思っていたが、結構遅くなってしまいそうなので止めた。 東京は夜遅くまで雨が降るという予報では無かったが天気は悪く万が一ということがある。 それに 2 日目も走り込んでしまうと疲れてしまう。 最近では悩んだ時は無理して走り回るより少し足りないかな、くらいのところで帰るようにしている。

富士山スカイラインを走ったが今日は 5 ℃ の表示が出ていた。 とても寒い。 いつも早朝に通っていたので入れなかったのだが「森の駅 富士山」に初めて入った。 富士山みやげが売っているし、食堂もある。 何より外と違ってとても暖かいし施設内は綺麗だ。 富士山のオアシスのような存在だ。

そのまま御殿場から高速に乗り帰宅した。 途中の用賀 PA では首都高特有のいろいろ自販機で売っているコーナーがあったのでオールドファッション (ドーナツ) を食べながら 300 円のコーヒーを飲んだ。 この 300 円のコーヒーというのはあの自販機で豆を挽いて作る「ちょっと高級なコーヒー」で、割とお客さんが作っているのを見ていつかやってみたいと思っていた。 ただ、私は自宅でエスプレッソメーカーでコーヒーを淹れているので、どうしてもそれと比べてしまってこの 300 円のコーヒーでも「大して美味くない」と思ってしまった。 自販機で 300 円も払ったのだからそれなりにリッチな気分になるのかと思ったが、全然そんなことはなかった。 次からは自販機でコーヒーは飲まないことにする。

大井川鐵道

静岡県焼津市方面に 1 泊 2 日のツーリングに出かけた。 まず高速を使用して島田金谷 IC まで一気に行ってしまう。 この時点で 8 時半だった。朝早く高速で一直線に行ってしまえばなかなか速い。 すぐ近くのセルフスタンドでガソリンを入れ大井川鐵道方面に走り出した。 まず千頭駅に道の駅があるのでそこを目標に走っていったのだが、事前情報のような秘境感も無いし至って普通の走りやすい田舎道が続く。 少々拍子抜けしていたところで道の駅に着いてしまった。

道の駅「奥大井音戯の郷」は音をテーマにしたミュージアムがあったのだが、有料 (500 円) だったのと別に博物館を見る気分ではなかったのでパスした。 また今度来る機会があったら入ってみる。 丁度千頭駅から列車の発車アナウンスが聞こえた。 オールドスタイルなデザインの車両はなかなか風情がある。

また、道の駅に廃車になった列車を使用した休憩所があったので、そこでしばらく休んでから出発した。

奥大井湖上駅

奥大井湖上駅

大井川鐵道は千頭駅から先 (井川線) と前 (大井川鐵道本線) で分かれており、その井川線のほうが秘境感が漂うのは事前に調べて知っていた。 中でも「尾盛駅」というのが日本でもトップクラスの秘境駅で Google Map を確認すれば分かるが駅へ続く車道はおろか歩道のようなものも存在しない、まさに陸の孤島と呼ぶに相応しい駅になっている。 私は秘境駅マニアではないのでわざわざこの駅まで行ったりはしないが、ロマンを感じるのは確かだ。

尾盛駅を除いて私が注目していたのが写真の「奥大井湖上駅」で、こちらは駅が中央の島の中にあり、そこに対して電車のレールが敷かれているという何とも独特な雰囲気を醸し出している駅だ。 写真のレールの外側に歩道があり島の外に歩いていくこともできるので、時間はかかるが車道側 (写真を撮った位置) から駅まで歩いていくことも可能だ。 電車で来る人が多いらしく、人が駅の方角から歩いてきていた。 有名なスポットなのだろう。写真撮影をしている人が多かったし、自分も女性 2 人組に撮影を頼まれた。

そのまま最終地点の「畑薙第一ダム」を目指して進んでいった。 やはり千頭駅を過ぎたあたりから道の様相が変わり、常に断崖絶壁の山肌を走っていくような感じになったし大きい落石を何度も目にすることになった。 確かに悪路なので車だとかなり苦しいだろう。

井川大橋と畑薙第一ダム

井川大橋

畑薙第一ダムへ向かう途中に印象的な橋を見つけたので何となく撮影した。 この井川大橋は 2 トンの車まで通行することができるらしいのでバイクでも通行できるはずだったのだが、どうにも怖いので止めておいた。 歩いて途中まで行って引き返すぐらいにしておいた。

畑薙第一ダムに着いたのだが、とにかく地形の高低差がすごくてとても怖い。 ダムマニアにとってはたまらないダムなのかもしれないが、私としてはダムカレーや展示物など一切ないダムだったので少々物足りない感じだった。 最もこんな僻地のダムにそんな食堂や展示物を用意してもほとんど誰も来ないとは思うが……。 というわけで、ここは 1 回来て満足すれば十分なところで、次回以降は奥大井湖上駅まででいいような気がした。 悪路は所々にあるに留まりバイクで来ればそこまでキツイ道のようには思わなかったが、道のりがあまりにも長過ぎる。

お昼時なのに食事も取れないしここに来るまでの道に食堂もなかったしどうしようかと思ったが、途中に綺麗な温泉宿があったのでそこで食べられないかと期待しつつ帰路についた。

白樺荘

白樺荘 刺鹿定食

綺麗な温泉宿は「白樺荘」だった。 日帰り温泉に浸かることができるようだったが、その日は暖かかったしホテルに宿泊予定なので止めておいた。 食事ができるかどうか受付の人に聞いたところ「できる」とのことなので食堂に入っていった。

食堂の中はほぼダム関係者で埋め尽くされているようだった。 私一人が旅行者で完全アウェーといった状況だった。 何となくまごまごしつつカウンターの上にメニューが張ってあるのに気づきしばし眺める。 「刺鹿定食」1,550 円。 ちょっと高いが鹿肉が食べられるのか、これにしよう、と頼んだ。 後からダム関係者が続々と来るが誰もこの「刺鹿定食」は頼まずラーメンや親子丼ばかり頼んでいく。 なるほど、これは極稀に来る観光客向けメニューだったのか、と一人で納得した。

鹿肉の刺し身だが、まだ完全に解凍されておらず少し氷っぽかった。 淡白な馬刺しといった感じで、確かに珍しいが美味しいかと聞かれると微妙なところだった。 やはり観光客メニューだったようだ。

ダム関係者で埋め尽くされた食堂の中の飲み会がどうの、などの他愛もない話に耳を傾けながら「こういう秘境の中にも確かに人が毎日一生懸命仕事して、こうやってつかの間の食事時を楽しんで生活しているんだな」と何となく嬉しくなった。 私は山の中で行き止まりになっているような無名の県道をツーリングをするのが好きだが、どこに行っても多かれ少なかれ必ず人の姿があり、確かにそこで人生を全うしている。 それを見ていろいろ想像するのも面白い。 今回の食堂のように完全アウェーであっても仲間外れ感はなく、妙な安心がある。 孤独のグルメ的な感じと言ったらいいのだろうか。

昼食を終え、そのままホテルを目指した。 ちょっと早いので途中に適当に見えた滝に寄ることにした。

権現滝

権現滝

写真は権現滝という滝だが、何となく写真写りが悪いように思えるがこれが仕方がないところで、岩の間をぬって下に落ちていく滝だった。 ちょっとどうかという感じだったが、こういう肩透かしを食らうのもまた自由気ままなツーリングらしくて悪くはない。 また、ここの滝に至るまでの道がダムまでの道とはまた違った感じの悪路で、路上河川 (洗い越し) が十数箇所もあるように思えた。 バイクが汚れたが致し方ないところだ。

そのままホテルを目指して 15 時過ぎには着いてしまった。

ホテル nanvan 焼津

ホテル nanvan 焼津

2 食付きで税込 7,000 円という安さと駅から距離があるので駐車場が充実しているのがいいなと思い予約したのだが、このホテルはとても良かった。

  • 館内や部屋が綺麗
  • カードキーがとても使いやすいし外出時にフロントに渡す必要もないし連絡する必要もないし門限もない
  • 朝食バイキングでマグロの漬けが食べ放題
  • この価格帯にしてはスタッフの対応がとても丁寧
  • ユニットバスだが入浴剤が無料でもらえる
  • 隣 (徒歩 1 分) にセブンイレブンがあるので酒やツマミなど買い放題

個人的にツーリングで泊まった中ではベスト 2 に入るホテルだろう。 こちらに来る機会があったらまた泊まりたい。

なんばん丼

ホテル nanvan 焼津 なんばん丼

この「なんばん丼」はホテルの夕食だ。 このホテルは基本的には朝バイキングのみなのだが + 1,000 円弱でこの夕食をつけることができるし、他のメニューとしては寿司なども用意されている。 17 時以降にフロントに行くと受け取ることができ、そのまま朝食の会場で食べてもいいのだが部屋で食べることもできる (容器はチェックアウトまで部屋にそのまま置いておけばいい)。 外部の業者に委託して冷蔵のまま配送してもらうのだろう。 1,000 円弱でこの海鮮丼が食べられるのはとてもいい。 ただ、このあたりは外をちょっと行けば飲食店がいくつかあったりするので外出してそちらで食べてもいいだろう。

ご飯を食べて一人で晩酌して 22 時には寝てしまった。

明日金曜日に有休を取得できたので久々に 1 泊 2 日のツーリングに出かける予定だ。 最初は福島の磐梯吾妻スカイラインを走ろうと思っていたのだが、東北地方の土日の天気が思わしくない上によくよく調べてみると 4 月上旬冬季閉鎖解除予定だった磐梯吾妻スカイラインがまだ残雪があるということで解除されていないらしかった。 折角泊りがけで行って走れないのは最悪なので、場所を南側に切り替えることにした。

南側といっても前回のツーリングで分かった通り、林道 (クリスタルラインやスーパー林道など) やビーナスラインのような展望がいい観光道路は多くがまだ冬季閉鎖中となっている。 まだ泊りがけのツーリング計画には早かったのかもしれない。 悩んだ末、今回は大井川鐵道沿線をトレースすることにした。 ここはかなりの秘境路線で車では通る気にならないくらいの狭い道らしいのでバイクで走るには最適だ。 冬季閉鎖に関しても多分大丈夫だと思う。 途中から冬季閉鎖されているかもしれないが、そうしたら諦めて戻ればいい。

大井川鐡道沿線の区間がかなり長いので 1 日目は新東名高速道路の島田金谷 IC まで行ってガソリンを補充してからこの大井川鐵道に沿って走って戻るくらいでちょうど夕方になるだろう。 出来ればその先の畑薙第一ダムあたりまで行ってみたいが冬季閉鎖されている気がしないでもない。

アノテーションを定義する方法が面倒な時

Spring Boot でのバリデーション実装に関しては昨日の記事に書いた。 今回は @NotBlank@Size など標準で用意されているアノテーションだけではまかないきれないようなバリデーションを行いたい時にどうするかについて書く。

まず、独自のバリデーションを行いたい場合は自分でアノテーションクラスを作成して Form クラスの対象フィールドに定義するといった方法がある。 これに関しては「Spring バリデーション 独自」などで検索すればいくらでも出てくるのでここでは言及しない。 アノテーションクラスを定義すればアノテーションを付与するだけでどこにでも使えるようになるので便利なのだが、私は以下の理由で後述の方法をとることにした:

  • 普遍的に使うのではなくただ 1 箇所で使いたいだけなのにアノテーションクラスを定義して使うのは面倒
  • RepositoryService などのモデル層が絡むようなバリデーションを使いたい

尚、自分でガリガリ書けば (Spring Validation を使用しなければ) どのようなバリデーションも思いのままだが、ここではそういうことではなくあくまで BindingResult の対象のフィールドにエラーが入った状態で Thymeleaf テンプレートが表示され、対象のフィールドに th:errors="(Form フィールド名)" でエラー表示がされるといったように組み込みのバリデーションと同じ流れで表示されるようにしたい。

BindingResult.rejectValue()

最初 BindingResult.addError() というメソッドを見つけたので何度か試していたのだが、全くうまくいかないので諦めて検索したら正しくは BindingResult.rejectValue() というメソッドを使用するのだという情報を見つけた。 このメソッドは rejectValue({Form フィールド名}, {messages.properties キー}) のようにして任意のタイミングで messages.properties (設定を変更していない場合は ValidationMessages.properties) に定義されているバリデーションエラーメッセージを対象のフィールドに対し設定することができる。 また、バリデーションエラーにプレースホルダがある場合 (例えば「{0} には日付の形式で入力してください。」のような) に rejectValue({Form フィールド名}, {messages.properties キー}, {プレースホルダに渡したい引数の配列}, {デフォルトメッセージ}) のようにして引数を渡すこともできる。

実装例

ここでは例として「同一日付に対する記事は登録できない (但し更新時は自分自身を対象外とする)」というバリデーション実装を行う。 messaegs.properties に以下リソースが定義されているものとする:

validation.date-format={0} は日付形式で入力してください。
validation.date-already-registered=その日付の記事は既に登録されています。

コントローラ側に組み込みのバリデーションを実施した後今回の独自バリデーションを以下のように実装する:

/**
 * 記事を追加する.
 *
 * @param model モデル
 * @param form PostForm
 * @param result BindingResult
 * @param id postId
 * @return template 名
 */
@PostMapping("/posts/add", "/posts/{id}")
fun savePost(model: Model, @Validated @ModelAttribute("form") form: PostForm, result: BindingResult,
        @PathVariable("id") id: Int?): String {
    model.addAttribute("tags", tagService.findAll())

    // 組み込みバリデーションエラーに引っかかった場合
    if (result.hasErrors()) {
        return "/posts/add"
    }

    // 日付の形式が間違っている場合はエラー
    val date: LocalDate
    try {
        date = LocalDate.parse(form.date)
    } catch (e: DateTimeParseException) {
        e.printStackTrace()
        result.rejectValue("date", "validation.date-format", arrayOf("日付"), "")
        return "/posts/add"
    }

    // 既に登録されている年月日の場合はエラー (但し更新時は自分自身を対象にしない)
    val post2 = postService.findByDate(date)
    if (post2 != null && (id == null || id != post2.id)) {
        result.rejectValue("date", "validation.date-already-registered")
        return "/posts/add"
    }

    TODO("保存処理")
    return "redirect:/posts"
}

これで同一日付で記事を登録しようとした時に「その日付の記事は既に登録されています。」といったエラーメッセージが表示される。

導入と properties の統合及び UTF-8 化

まず build.gradle に以下を定義する:

dependencies {
    compile('org.springframework.boot:spring-boot-starter-validation')
}

これですぐに使えるわけだが、その前にバリデータの設定をする。 後述するがバリデーションメッセージに関してはデフォルトで ValidationMessages.properties に定義されているものを使用するのだが、これも後述するがバリデーションメッセージのプレースホルダに適用されるフィールド名は messages.properties のものが使用されるので 2 箇所に書くことになってしまう。 できれば messages.properties に両方書くようにしたい。 更にデフォルトで properties ファイルは UTF-8 エンコーディングになっていない。 これも UTF-8 に変更したい。

これを実現するには以下のような WebMvcConfigurer を実装したコンフィギュレーションファイルを定義する:

@Configuration
class Configuration : WebMvcConfigurer {

    /**
     * バリデータを返す.
     *
     * @return バリデータ
     */
    override fun getValidator(): Validator? {
        val source = ReloadableResourceBundleMessageSource().also {
            it.setBasename("classpath:messages")  // ValidationMessages.properties でなく messages.properties を使用する
            it.setDefaultEncoding("UTF-8")  // エンコーディングとして UTF-8 を使用する
        }
        return LocalValidatorFactoryBean().also { it.setValidationMessageSource(source) }
    }
}

ちなみに Spring 4 以前は WebMvcConfigurerAdapter というインターフェースを使用していたようだが Spring 5 (Spring Boot 2.0) では非推奨となり WebMvcConfigurer を使用するようになった。

messages.properties でなく messages.yaml などというファイルを置くとよろしくやってくれるのかどうか試したのだが駄目だった。 残念。

バリデーション例

例えば以下のように messages.properties に定義してあるものとする:

# フィールド名
date=日付
name=名前
tag=タグ
markdown=Markdown

# バリデーションメッセージ (デフォルトの差し替え)
javax.validation.constraints.NotBlank.message={0} を入力してください。

# カスタムバリデーションメッセージ
validation.max-length={0} は {1} 文字以下で入力してください。
validation.not-selected={0} を選択してください。

javax.validation.constraints.NotBlank.message に関してはバリデーションで使用する @NotBlank アノテーションでのエラー時に表示されるメッセージの差し替えである。 この場合 @NotBlank アノテーションのパッケージ名を含めたクラス名が javax.validation.constraints.NotBlank の為それに .message を加えたものを定義しておくとデフォルトメッセージの差し替えができる。 @NotEmpty@Size なども同様となる。

尚、こういう全体的に適用できるバリデーションメッセージだけでなく項目ごとに個別に指定したいバリデーションエラーメッセージがある。 例えば @Size によるバリデーションエラーは「0 文字以上 64 文字以下」のような表示になってしまうが、多くの場合は「0 文字」は不要で最大桁数のみ通知すればいいはずだ。 こういう場合に任意のプロパティ名でカスタムバリデーションメッセージを定義しておく。

フィールド名に関してはフォームの POST 時に使用する Form インスタンスのフィールド名と同じにしておく。

Form

class PostForm {

    // 必須
    @NotBlank
    var date: String = ""

    // 必須かつ 64 文字以内
    @NotBlank @Size(min = 0, max = 64, message = "{validation.max-length}")
    var name: String = ""

    // 選択必須
    @NotEmpty(message = "{validation.not-selected}")
    var tag: Array<Int> = arrayOf()

    // 必須
    @NotBlank
    var markdown: String = ""
}

上記の例のようにバリデーションエラー時に表示されるカスタムメッセージを messages.properties を使用して指定したい場合は {validation.max-length} のような記法で書く。 {} を付けずに書くと任意の文字列を指定できるが、折角 messages.properties が使えるのにハードコーディングすることもないだろう。

Controller

@Controller
class MyController {

    /**
     * 記事追加画面を表示する.
     *
     * @param model モデル
     * @param form PostForm
     * @return template 名
     */
    @GetMapping("/posts/add")
    fun addPost(model: Model, @ModelAttribute("form") form: PostForm): String = "posts/add"

    /**
     * 記事を追加する.
     *
     * @param model モデル
     * @param form PostForm
     * @param result BindingResult
     * @return template 名
     */
    @PostMapping("/posts/add")
    fun savePost(model: Model, @Validated @ModelAttribute("form") form: PostForm, result: BindingResult): String {
        if (result.hasErrors()) {
            return "/posts/add"
        }
        
        // TODO 登録処理
        return "redirect:/posts"
    }
}
  • 登録画面初期表示時の GET と POST を分ける (Form に対するバリデーション指定の為)
  • BindingResultForm の直後の引数として定義する (※位置が違うと正しく機能しないので注意) とメソッド内で result.hasErrors() でバリデーションエラーの有無を取得できる (BindingResult がないとメソッドの中まで処理が進まずに弾かれてしまう)
  • 登録画面初期表示時の GET のメソッドの方にも Form を含めたほうが良い (フォーム項目の初期データの表示時に Form に直接値をセットすれば良い)
  • FormModel は HTML 内の <form> によって POST される項目か否かで使い分けると良さそうに見える

Thymeleaf

<form th:action="@{/posts/add}" method="POST" th:object="${form}">
    <div>
        <input type="text" id="date" name="date" th:value="*{date}" th:classappend="${#fields.hasErrors('*{date}') ? 'is-invalid' : ''}">
        <div class="invalid-feedback" th:if="${#fields.hasErrors('*{date}')}" th:errors="*{date}"></div>
    </div>
    <div>
        <input type="text" id="name" name="name" th:value="*{name}" th:classappend="${#fields.hasErrors('*{name}') ? 'is-invalid' : ''}">
        <div class="invalid-feedback" th:if="${#fields.hasErrors('*{name}')}" th:errors="*{name}"></div>
    </div>
    <div>
        <select class="custom-select" id="tag" name="tag" multiple="multiple" th:classappend="${#fields.hasErrors('*{tag}') ? 'is-invalid' : ''}">
            <option th:each="t : ${tags}" th:value="${t.id}" th:text="${t.name}" th:selected="${#arrays.contains(form.tag, t.id)}"></option>
        </select>
        <div class="invalid-feedback" th:if="${#fields.hasErrors('*{tag}')}" th:errors="*{tag}"></div>
    </div>
    <div>
        <textarea id="markdown" name="markdown" class="form-control" rows="16" th:classappend="${#fields.hasErrors('*{markdown}') ? 'is-invalid' : ''}">[[*{markdown}]]</textarea>
        <div class="invalid-feedback" th:if="${#fields.hasErrors('*{markdown}')}" th:errors="*{markdown}"></div>
    </div>
    <div>
        <input type="submit" value="保存"/>
    </div>
</form>

<form> の内容を全部書いたので若干見づらいが重要なのは ${#fields.hasErrors('(Form フィールド名)')} でそのフィールドにエラーがあるかどうかが取得でき th:errors="(Form フィールド名)" で対応するエラーメッセージが要素内のテキストノードに格納されるということだ。 コードにも例示したがエラーの有無で入力フォームの見た目を変えたい場合は th:classappend を使用して class 属性を追加して CSS で見た目を変更すれば良い。

ここまで定義した内容で名前以外すべて空、名前は最大文字数をオーバーした状態で submit すると以下のようにエラーメッセージが表示される:

  • 日付 を入力してください。
  • 名前 は 64 文字以下で入力してください。
  • タグ を選択してください。
  • Markdown を入力してください。

Android 開発で未だに完全な Java 8 が使えないということもあり (Stream API でさえも最低ビルドターゲットを引き上げないと使用することができない)、どうしても Java 8 Time API への移行ができずにいにしえの DateCalendar などを駆使してイマイチな日付処理を行う癖がついてしまっていた。 Spring Boot ならばちゃんと Java 8 Time API が使用できるので古い API は捨て去りたい。

しかし Thymeleaf が標準で備えている #dates.format() などのユーティリティメソッドは Date を対象としておりそのままでは LocalDateTime などは使用することができない。 これでは困ってしまうので拡張機能を導入する。

解決法

build.gradle に以下を追加する:

dependencies {
    compile('org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.1.RELEASE')
}

どこでもいいのだが component-scan 対象パッケージのどこかに以下のような @Bean 定義を置く:

@Configuration
class Configuration {

    /**
     * Java 8 Time Dialect を返却する.
     *
     * @return Java 8 Time Dialect
     */
    @Bean
    fun java8TimeDialect() = Java8TimeDialect()
}

これだけで Thymeleaf 上で LocalDateLocalDateTime を扱うことができるようになる。 例えば #date.format() にあたるのは #temporals.format() で以下のように書く:

<div th:text="${#temporals.format(firstDate, 'yyyy/M/d')}"></div>

他の使い方は Thymeleaf - Module for Java 8 Time API compatibility の GitHub に書いてあるので参照すればすぐ分かるだろう。

今日は妻と子どもたちがママ友とディズニーシーに行くというので留守番をしていた。 適当な時間に買い物に行き適当な時間にお風呂にはいり適当な時間につまみを食べながらビールを飲む。 こういう自由な休日が好きだ。

そういえば久々にキャプテンクロウ・エクストラペールエールを飲んだがやっぱり美味い。 グレープフルーツ果汁が入っているのではないかと思わせるくらいの柑橘臭がする。 これをホップと麦芽のみで表現しているのはすごい。

群馬県道 177 号線は分断されている

「上毛三山パノラマ街道」を通ってみようと思いツーリングに出かけた。 「じょうもうさんざん」と読むようで最初読めなかった。 妙義山・榛名山・赤城山を結ぶので三山らしい。 とはいえ通るだけだとすぐ終わってしまうので、まず本庄児玉 IC まで高速で移動し国道 462 号線でちょっと行った先の群馬県道 177 号に行ってみた。 群馬県道 177 号終点に「御荷鉾山不動尊」があったのでそこを目指してみたが、結構歩くような感じだし「クマ注意」の看板が見えたので何となく止めておいた。 これだけだと面白くないのでそのまま林道区間に入っていきどこまでいけるか試してみた。

群馬県道 177 号線林道

写真は林道終点だが道路状態は劣悪だった。 尚、現状の Google Map でも道が描かれていない区間である。 この間の雪スタックで少し懲りたので今回は割と用心深く進んでいった。 写真のところで例えば坂になっている場合 BOLT だと重すぎて切り返せなかったら非常にまずいのだが今回は何とかなった。

群馬県道 177 号は分断されている。 本当はこの林道が通れれば北側の 177 号に出られるのだが、見ての通り通行止めだった。 戻っていった先のスーパー林道で南側に抜けようと思ったのだが、よく見ると 4 月 20 日まで冬期通行止めだった。

県道 71 号線

今度は県道 71 号線に南側からアプローチした。 この県道も分断道路のようで、林道を通して行き来できるようだった。 南側の入口付近がとても急な坂になっている上に道が狭く、「険道」の部類に入るように思えた。 先程の県道 177 号もそうだったが、この道路も私が通っている間 1 台の車にもすれ違わず独占状態だった。 だからこういう観光に無縁の行き止まり県道が大好きだ。 途中「みかほ高原荘」という建物があったが、前まで行くとやはり「冬季休業中」の張り紙が見えた。 なるほど、それではこの道路を通る意味はないわけだ。

ここも終点近くで西側にスーパー林道で抜けたかったのだが、やはり 4 月 20 日まで冬期通行止めの看板が出ていた。 このあたりの道路を楽しむにはまだ時期が早かったのかもしれない。

上毛三山パノラマ街道

昼は近くのコンビニでおにぎりを齧り、そのまま県道 196 号線に南から入っていった。 ここは「森林公園さくらの里」があるのだが、そこに行くまでの道中にも所々に桜が咲いていた。 私の住んでいる地域は既に散ってしまっていたが、ここは今が見頃のようで通る車も多かった。 森林公園さくらの里では花見をしている観光客も見受けられた。 私も少し停車して山の桜を楽しんだ。

そして、この道路は「パノラマ街道」の名に恥じない絶景が楽しめた。 眼前に見える妙義山のゴツゴツとした岩肌がとても印象的で素晴らしかった。 桜や紅葉の時期でなくても走ると楽しいだろう。 むしろ観光客が少ないであろう夏に走るといいかもしれない。

帰りに妙義神社の表示が見えたが、少し疲れたのでそのままスルーした。 今度来る機会があったら登ってみたい。

server.xml の場合

Tomcat はデフォルトで PUT と DELETE のリクエストボディが無効になっているらしく POST と同じような感じでフォームデータを PUT, DELETE してもすべてクリアされてしまう。 これを有効にするには server.xml に以下の様に設定を行う:

<Connector port="8080" protocol="HTTP/1.1" 
           connectionTimeout="20000"
           redirectPort="8443"
           parseBodyMethods="POST,PUT,DELETE"
           URIEncoding="UTF-8" />

Spring Boot 組み込み Tomcat の場合

Spring Boot で Application クラスを実行して組み込みの Tomcat が立ち上がった際もこの設定を有効にしたい。 この場合 application.yaml ではなく @Configuration アノテーションを付けたクラスに @Bean として TomcatServletWebServerFactory を返すメソッドを書く:

@Configuration
class Configuration {

    /**
     * 組み込み Tomcat のデフォルトで PUT, DELETE に Request Body が許可されていないので許可する.
     *
     * @return TomcatServletWebServerFactory
     */
    @Bean
    fun tomcatEmbeddedServletContainerFactory(): TomcatServletWebServerFactory = object : TomcatServletWebServerFactory() {
        override fun customizeConnector(connector: Connector?) {
            super.customizeConnector(connector)
            connector?.parseBodyMethods = "POST,PUT,DELETE"
        }
    }
}

Tomcat の他の設定をいじりたい場合も同じようにここに追記することができるようだ。

JPA の Entity では単一キーである id 列を持たせるような構造を定義することが出来るが SQLite の INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT な列に関して適切な定義がよく分からなかったのでメモ。 例えば以下のようなテーブルがあるとする:

create table comments (
    id integer not null primary key autoincrement,
    name varchar(16) not null,
    body text not null,
    created datetime not null,
    modified datetime not null,
);

このような AUTOINCREMENTPRIMARY KEY があると SQLite は内部的に sqlite_sequence というテーブルに各テーブルのシーケンスを格納するという挙動をする。 sqlite_sequence テーブルの DDL は以下のようになっている:

CREATE TABLE sqlite_sequence (name, seq);

name にはテーブル名、seq には現在のシーケンス値が格納される。 この sqlite_sequence テーブルを Hibernate 側に教えてやればよい。 この場合の Entity 定義は以下のようになる:

@Entity
@Table(name = "comments")
data class Comment(
        @Id
        @GeneratedValue(generator = "sqlite_comments")  // Generator 名 (何でもよい)
        @TableGenerator(
                name = "sqlite_comments",  // @GeneratedValue.generator と合わせる
                table = "sqlite_sequence",  // SQLite のシーケンステーブル名と合わせる
                pkColumnName = "name",  // sqlite_sequence のシーケンスカラム名 (name 固定)
                valueColumnName = "seq",  // sqlite_sequence のシーケンス値名 (seq 固定)
                pkColumnValue = "comments",  // sqlite_sequence.name に格納されている値 (テーブル名)
                initialValue = 1,  // シーケンス初期値. 多くの場合 1
                allocationSize = 1  // AUTO INCREMENT される場合の増減値. 何故かデフォルト 50 になっているので 1 を指定する
        )
        var id: Int? = null,
        var name: String = "",
        var body: String = "",
        @CreatedDate var created: Date = Date(),
        @LastModifiedDate var modified: Date = Date()
)

素麺を消費するようになる季節

暑くなってくると素麺の出番だ。 以前 1 回安い素麺を買って食べた時に本当に美味しくなくて後悔した。 素麺ではやはり揖保乃糸がバランスが取れているように思う。 どうせ大量に消費するし長持ちするのだから次の楽天お買い物マラソンで箱買いしようかと考えている。 最近子どももだんだん食べる量が多くなってきたので尚更大量買いが有効のように感じる。

そういえば前「そうらーめん」という食感と麺の細さがそうめんではあるがかん水を使っているのか麺が黄色がかっており、麺つゆに添付の香味油を入れてラーメン感を出す商品があった。 結構好きだったのだが今はもう売っていないのだろうか。

後全く関係ないがハインツのレッドチェダーソースも欲しい。 シェイクシャックのチーズがけポテトやこの間のチェダーチーズバーガーで使っているようなレッドチェダーソースを家でも使えると、フライドポテトを作った時にかけるなどすれば子どものいいおやつになったり、時には酒の肴になりそうだ。

見物渋滞

そういえば首都高を通行している際に渋滞の表示が見えたのだが「見物渋滞」という表示になっていた。 私はこの言葉を知らなかったので面食らってしまった。 見物渋滞とは何なのだろうか、桜はもう散ってしまったのに、などと呑気に構えていた。

渋滞の終点あたりまで走ると反対車線側がもの凄い玉突き事故になっていた……。 そこで合点がいった。 見物渋滞とは「対向車線で起こった事故をつい減速して見てしまった結果起こる渋滞」のことだと。 高速上では上り坂などで少し減速するだけでも渋滞の原因になる。 また一つ勉強になった。

川口の息の長いグルメバーガーショップ

Tity Diner あふれるチェダーチーズバーガー

Tity Diner は 2 年ぶりくらいだっただろうか、と思い Blog 記事を検索してみたが出てこなかった。 ということは 3, 4 年ぶりくらいだろう。 川口のお店ということでそれなりに近いのだが、なかなか行けていなかった。 平日に行ったがお客さんは私一人だった。 これだけ息の長い店でも平日はお客さんが少ないのだろう。 グルメバーガーショップにはよくあることなので特に驚きはなかった。

この「あふれるチェダーチーズバーガー」はチェダーチーズに加えてチェダーチーズのソースもふんだんに使用するというまさにチーズまみれと言ったほうが良さそうなバーガーだった。 cotton's の時も思ったのだが、このチーズソースの甘い味わいが何とも言えない。 指定店でオーダーしているというこだわりのバンズと 150 g パティの重量感もあり、とても美味しくいただけた。 駐車場はないが、アリオ川口の買い物ついでに来れるのがポイントだろう。

それにしても、昼にマクドナルドに行ったのに夜もハンバーガーを食べているあたり、私もまだまだハンバーガーが好きなようだ。

アルピタンは漢方の働きでアルコールなどによる頭痛を抑える粉薬だ。 何度か試してみたのだが、残念ながら私にはほとんど効果がなかった。 ビールで「自分がこれ以上飲んだら次の日残る量」というのは大体分かっているのだが、飲む前にアルピタンを飲んでその次の日残る量を少し超えた位飲むとやはり次の日に頭が痛くなってしまったし、二日酔い状態で飲んでも全く効果がなくロキソニンを飲んだほうが余程効果があった。 ネット上を検索すると効いたという人もいるので個人差があるのだろう。

自分としてはこういった薬に頼るより、以下の一般的によく知られていることを遵守した方が効果が高いという結論になった:

  • 飲んだ後すぐ寝ない
  • 短時間で飲まずにゆっくり時間をかけて飲む
  • 次の日残りそうな場合はなるべく水を飲んで薄める
  • 空きっ腹で飲まない

健康を考えて 1 日ロング缶 1 本 (500 ml) までとしておくのがいいが、喉元過ぎれば熱さ忘れるというのかしばらく経つとまた飲みすぎてしまうのが困ったものだ。 いい加減に学習したい。

工場見学

新幹線で仙台に行ったのでホテルまでの移動はシャトルバスを使用していたのだが、ホテルから仙台駅までのバスの予約が早い時間でしか取れなかったので割と余裕なく朝食のバイキングを食べてそのままチェックアウトした。 シャトルバスに乗って仙台駅へ行った後は、キリンビール仙台工場の無料送迎バスに合わせるために多賀城駅まで電車で移動してバスに乗った。 バスが 1 時間に 1 本なので時間を合わせるのがなかなか難しい……。

キリンビール仙台工場見学は事前予約が必要とはいえ無料で、見学途中に一番搾り麦汁と二番絞り麦汁の飲み比べがあったり、最後に一番搾りの試飲があったりとかなり満足できる内容だった。 アルコールが飲めない子どもにも麦汁の段階ならば飲めるし、最後はジュースが用意されていた。 昼食は隣接するレストランで食べようとしたのだが、残念ながら予約制らしく入れなかった……。

仙台うみの杜水族館までどう行こうかというところで、一度シャトルバスで駅に戻ってから 1 駅移動してまたシャトルバスに乗るという手もあったのだが、工場から水族館までそこまでは距離がないので歩く方を選択した。 途中の道で昼食を食べようとしたのだが何もなかったので、結局水族館まで来て近くの食堂で食べた。

仙台うみの杜水族館

水族館は子どものリクエストで入れた。 水族館好きの方だとまた意見が違ってくると思うのだが、まあ普通に楽しめる水族館だった。 歩きすぎて疲れてしまったので、イルカとアシカのショーまでの待ち時間にこれ幸いと席を取って座りながらなるべく体力を回復した。

久々に新幹線に乗れて楽しかったことは楽しかったのだが、やはり現地に着いた後の移動が電車やバスだと厳しかった。