東京23区のスーパー銭湯全制覇を目指すアプリ「スパコレ」をリリースしました

はじめに
東京23区のスーパー銭湯全制覇を目指すアプリ「スパコレ」をリリースしました♨️
この記事では、アプリの使い方と開発の振り返りについてまとめています。
アプリの概要
東京23区にあるスーパー銭湯を訪れて、GPS連動のチェックイン機能で訪問記録を残すことができます。
このアプリでの「スーパー銭湯」の定義は以下の通りです。
- サウナ特化型の施設ではないこと
- 入浴料金が公衆浴場入浴料金ではないこと
- 男性専用・女性専用の施設ではないこと
全29施設を制覇できるか、是非チャレンジしてみてください🔥
アプリを作ったきっかけ
広々とした湯船に浸かってリラックス、ゆったり休憩スペースでぼ〜っと過ごす時間、そして心もお腹も満たされる美味しいご飯!
どれをとってもスーパー銭湯は魅力的です。丸一日いても飽きることのない、まさに心と体のオアシスだと思っています!
だからこそ、もっといろいろな施設を訪れて、「最高」の更新を続けていきたいと思いました♨️
とはいえ、出不精な私にとっては、直前になると面倒くささが勝ってしまい、「また今度でいいか……」となりがちでした。
そこで、「出かけるきっかけ」や「目的」になるようなアプリを作ろうと思い立ち、開発をスタートさせました。
GPSを使ったチェックイン機能により、実際に足を運ばないと記録が残せない仕組みになっているのがこのアプリの特徴です👣
使い方
1. ログインする
トップページからGoogleアカウントでログインします。

2. 行きたいスーパー銭湯を探す
スーパー銭湯マップまたは施設一覧から探せます🔍
スーパー銭湯マップでは、現在地を中心に周辺の施設を表示します🗾


3. スーパー銭湯に行く
スーパー銭湯に到着したら「チェックイン」!
施設の200m以内に入ると、チェックイン可能になります👍
ボタンを押すだけで訪問日が記録されます✅

4. 目指せ全制覇!
スーパー銭湯を訪問するとトップページのスタンプカードに変化が!👀
ぜひ様々な施設に足を運んでみてください🙌

開発で使用した技術について
READMEに記載したので、興味がある方はご覧ください🙏
こだわったこと
ユーザーの使いやすさを考えること
ユーザーにとって直感的でわかりやすいUIになるよう意識しました。 ページを開いた瞬間に「次に何をすればいいのか」がすぐにわかるよう、迷わせない導線づくりを心がけました。
- そのページでユーザーに何をしてほしいのか?
- 情報量は多すぎず、シンプルにまとまっているか?
といったポイントに重点を置いて改善を重ねました。
また、何度もドッグフーディングを行い、使い心地を改善していきました。
自分の「面白い」を大切にすること
このアプリの前にも、「外に出かける理由をつくりたい」という思いから、別のアプリを開発していました。
しかし、プロトタイプを実際に使ってみたところ、「あまり面白くないな……」「ちょっと使いづらいかも」と感じてしまい、思い切ってその開発は中断することにしました。
当時は、「ここまで頑張って実装してきたのに、もったいなかったかな……」と少し後悔もありました。
それでも、思い切って区切りをつけて本当によかったと思っています。
スパコレでは実装中に壁にぶつかっても、「絶対にこれを形にしたい」「もっと良くしたい」という気持ちが、自分を動かす原動力になってくれたからです。
タスクを細分化すること
開発を進めるうえで、「あとは手を動かすだけ」という状態にしておくことを常に意識していました。
そのために、タスクはできるだけ細かく分けて管理し、迷わず手を動かせるようにしました。
後から見返したときにも「何を、なぜやったのか」がわかるように情報を残すよう心がけました。
苦労したこと
使いやすさの追求とデザイン
こだわったことの項目でも触れましたが、使いやすさとデザインの両立には特に時間をかけました。
もともとデザインやCSSには苦手意識があり、満を持してデザインレビューをお願いしたときの画面は、今振り返ると恥ずかしいです...。

そんな状態から、在籍しているフィヨルドブートキャンプのmachidaさんには毎週画面を一緒に見ながら丁寧にアドバイスをいただき、方向性を少しずつ整理しながら改善を重ねていきました。(machidaさんにはロゴのデザインもお世話になっています🙇)
この経験を通じて、「使いやすいデザインとは何か?」を改めて考えるきっかけになり、デザイナーの方々へのリスペクトがますます深まりました。
新しい技術を取り入れたこと
短期間で必要な機能を実装するため、フロントエンドには Hotwire を採用しました。
学習を始めると突き詰めたくなる性格なので、実装したいことベースでその都度必要な知識を学び、実装していくスタイルを取りました。
また、位置情報の扱いも初めての経験でした。
ブラウザをリロードさせずに地図を表示させたり、現在地が取得できない場合のエラーハンドリングなど、初めてのことだらけで問題を解決していく過程がとても楽しかったです!
このアプリの肝となるチェックイン可能範囲の算出では、地球って丸かったんだ...と改めて実感したのもいい思い出です🌏
テスト
ユースケースを洗い出してテストを書き進めるのが大変でしたが、最終的にカバレッジ率は99.69%になりました。
100%を目指していたわけではなく、「テスト実行時に何%くらいか分かればいいや〜」という軽い気持ちでカバレッジツールを導入していたのですが、最終的に高い数値になって自分でも驚きました😳
「スーパー銭湯」の選定
地道に検索を重ねて施設を調べ、その緯度経度を初期データとして投入しました。
スーパー銭湯情報のまとめサイトも活用しましたが、結局一つ一つの施設を確認する必要があり、地道で泥臭い作業を行いました。
今後やりたいこと
対象施設を増やしたい
現在は東京23区限定ですが、対象エリアや施設を拡大し、より多くの方に楽しんでいただけるようにしたいです😌
もっと使いやすさを追求したい
施設に近づくだけで自動的にチェックインされるなど、より簡単に使いこなせる仕組みを実装したいです💪
モーダルを追加
チェックイン回数や訪問施設数に応じて特別なアクションを表示し、スーパー銭湯巡りがもっと楽しくなる仕掛けを作っていきたいです!
おわりに
リリースするまでに思っていた以上に時間がかかりました😭
プロトタイプ自体は1ヶ月ほどで形になっていたのですが、「本当にユーザーは楽しんでくれるだろうか……?」「自分だけが面白いと思っているだけかもしれない……」と、何度も立ち止まっては悩みました。
それでも何とかリリースまでたどり着けたのは、在籍していたフィヨルドブートキャンプの皆さん、Rubyコミュニティの皆さん、友人達、そして夫——支えてくださったすべての方のおかげです。
心から感謝しています。本当にありがとうございました。
これからはスパコレを使ってスーパー銭湯巡りをしつつ、前向きに転職活動を進めていこうと思います♨️
bootcampアプリにおける環境ごとのデータの違い
bootcampアプリに新規機能を実装したのですが、production環境にマスタデータが反映されなくて詰まっていました。
本記事では備忘録も兼ねてデータの環境差分をまとめました。
production環境のデータを更新する場合
先に結論。
data-migrateというgemを使ってデータを更新する必要があることを念頭におきましょう。
production環境のデータを更新するPRを作成する場合、「fixturesファイルの追加+data-migrationファイルの追加」をする必要があります。
bootcampアプリでdata-migrate gemを使用している経緯はFBC wikiを参照してください。
環境毎のDBデータの違い
local, staging, productionでそれぞれDBに入っているデータが異なります。
| 環境 | 状態 | 補足 |
|---|---|---|
| local | fixturesのデータが投入されている(READMEを参考にセットアップした時) | bin/setupで、db:prepareが実行されている。これは内部的にdb:seedを実行しているので、fixturesのデータがDBに投入されている |
| staging | fixturesのデータが投入されている。data-migrateのファイルは実行されていない | .cloudbuild/cloudbuild-staging.yamlでdb:seedのみが実行されているため |
| production | data-migrateのファイルが実行されている。fixturesのデータは投入されていない | .cloudbuild/cloudbuild.yamlでdb:migrate:with_dataのみが実行されているため |
つまり、fixturesファイルの追加のみではproduction環境のDBにデータが投入されません。
まとめ
- production環境でデータを使わない場合
- => fixturesファイルを追加し、
db:seed実行でデータを投入する
- => fixturesファイルを追加し、
- production環境でデータを使用する場合
- => data-migrateファイルを作成して、localでDBデータが更新されることを確認(
VERSION=xxxx bundle exec rake data:migrate:upとversionを指定する。local環境でdb:migrate:with_dataを実行しようとしても現状動かない)することを忘れない。fixturesファイルを追加し、db:seed実行でデータを投入する
- => data-migrateファイルを作成して、localでDBデータが更新されることを確認(
最後に
localとstaging環境で動作確認できたからといって、production環境でもOKでしょ!と油断しない。 当たり前ですが、それぞれの環境で何が実行されるのかを確認しましょう。
Discordでバグの報告を受けて、悪い意味で「そいつの実装者、俺だもん」になりました。*1
RubyKaigi 2024事後勉強会でLTしました
概要
SmartHRさん主催のRubyKaigi 2024事後勉強会で初参加枠のLTをしました🎤
思い出を振り返りつつ、Quineに挑戦した経緯を書きました✏️
LT資料
こちらです🔽 今見たら予想以上に閲覧されててビビるな…。
改めて言っておきたいのですが、私がRubyKaigiで楽しめたのは、フィヨルドブートキャンプの援助でエモリハウス & ヤダハウスに滞在させていただいたのが大きいです🙇
本当にありがたかったです!お世話になりました🙏
登壇の経緯
アフターパーティーでmktakuyaさんに声を掛けられたことがきっかけです!
mktakuyaさん「初参加枠で登壇する人探しているんですよ〜」
私「いいっすよ〜(connpass参加登録ポチー)」
で、初めましての状態からものの数分で登壇が決まりましたw
折角のご縁だし良い機会だからやってみるか〜と軽いノリでOKしたのでした。(この当時、勿論話すネタは考えていない)
mktakuyaさん、SmartHRさん本当に素敵な機会をありがとうございました!
Quineに挑戦
まず作品の紹介をします。 スライドだとリポジトリを雑に示しているだけなので、スクショを貼って説明します。 LTでは2作品紹介しました。
その1
実行すると"2024"が表示される

パイプで渡しても実行できる

"next"を引数で渡すと"2025"に表示が変わる

パイプで渡しても実行できる

その2
実行すると"2024"が表示される(その1と同じ)

"matz"を引数で渡すと"20024"に表示が変わる...※1
これもパイプで渡しても実行できる

※1...RubyKaigi2024に参加した方ならわかるネタ。Matzが"2024"を"20024"と書き間違えてしまい弄られていたのでした。
補足
補足しておきたいのですが、このコードは厳密にはQuineと言えないんじゃないかなあと思っています。 Quineは「自分自身のソースコードを出力するプログラム」です。これらの作品はよくみるとコードが変わっているのがわかると思います。 赤枠の部分が変化している部分です。
引数なし

引数あり

なので「Quineを出力するコード」というのがより正確な表現だと思います。
このリポジトリで試せるので、良かったらCodespacesでサクッと試してみてください🔽
作成の経緯
RubyKaigi 2024のtompngさんのself TRICKに触発され、これは自分で作ってみたら絶対面白いじゃん!とノリノリで挑戦しました。 以下のサイトや書籍を参考にしました。
- Ruby で書く! Quine AA!
- Ruby でうどんげ Quine(と AA 型 Quine の作り方講座)
- クックパッド春の超絶技巧パンまつり 超絶技巧プログラミング編 資料
- RubyKaigi 2024 をきっかけに Quine に入門してみた
- あなたの知らない超絶技巧プログラミングの世界
コードを写経して感覚を掴み、引数で条件分岐させるアレンジを盛り込みました。 なので、どうやったらできるようになるんですか?の問いの答えはまず写経してみることですかねぇ...。それしか言えないかも。 これらの記事で丁寧に解説されているので詳細説明は割愛します。必見です。
私が苦戦したポイントは以下です。
- "2024"の"0"の部分は自分でAAを作成したので整形がしんどかった
- 画面を凝視して疲れた。AA職人さん凄いよ!
- AA化するソースコードの文字数とAAの文字数のバランス調整
- 本当は"2024", "2025", "20024"を合わせた1つの作品にしたかったのですが、文字数調整で断念しました
- 引数による条件分岐
- コード中で#を使うとコメントになってしまうので、
["_=\"#{ARGV[0]}\";"]'で表現している
- コード中で#を使うとコメントになってしまうので、
最後に
恐縮なのですが、最近いっぱいLTしてて凄いね〜と褒められることが多いです。 就職に有利だからですか?と質問されることもあるのですが、就職のためと考えると肝心の自分が楽しめないのであまり意識しないようにしています。そりゃ就職に繋がったら嬉しいけどね!! 発表はいまだに緊張するけど、もうウケた時が最高に気持ちよくて報われた気持ちになります。
他の登壇者のように技術のすごい話はあまりできていないかもしれないのですが、その分(?)オーディエンスと一緒に楽しむマインドの部分は自信を持っています💪 折角時間を割いてくれているのだから楽しませたいし、楽しみたいなあと思っています〜!
もし今後LTの機会があればフラッと聴きにきていただけるととても嬉しいです!!!
Shinjuku.rb #91でLTしました
概要
先週、Shinjuku.rb #91 好きなもの紹介LT大会でLTをしました🎙️
この記事では、以下について書きました✍️
- LTの感想
- GitHub ActionsのTips
LTの感想
地域rbでLTするのは2回目でした。 今回のテーマが「好きなもの紹介LT」だったのもあり、前回ほどは緊張しなかったです。
発表中の皆さんのリアクションが温かく、とっても話しやすかったです〜!
ご清聴ありがとうございました🙏
LT会を振り返ると、皆さん楽しそうに話す姿が印象的でした!
本当に好きなんだな〜と思える素敵な発表ばかりでした✨
また、2次会に誘っていただけたのも嬉しかったです!ご馳走様でした🙇♀️
LTの余談
実装の経緯について少し話します👀
凝っているGitHubのプロフィールページをよく見かけるのですが、あれ格好いいですよね...! 私も改造したい〜!と思い幾つか候補はあったものの、どうせなら可愛くしたいと自作に至りました💪
ずっと同じ見た目だと飽きが来そうなので、更新する方法を探していました。 下記の参考サイトを元に、GitHub Actionsを使って更新させました🙆♀️
参考:
ヘッダー・フッター画像の作成
ポケモンの画像から手動で色を抽出して、ヘッダー・フッター画像を作っていました。 どんな画像を作成するか?の部分でもかなり悩み、最終的にストライプ柄に落ち着きました。
一時期ポケモン図鑑とずっと睨めっこしていました😇
ドミナントカラー(画像のメインとなる色)を抽出するライブラリがあるので、これらを使うともっと楽に画像作成できそうです。
参考:
GitHub ActionsのTips
実装で気付いた意外と知られていない(?)Tipsを残しておこうと思います✍️
- 手動実行
- 定期実行
- タイムアウトの設定
手動実行
GitHubにpushした時に発火するイメージがあると思いますが、手動実行できます🆗
定期実行
こちらも驚かれたのですが、できます! ただ、タイムゾーンとcronの日時指定には注意が必要です。 そして、日時指定してもその時間ピッタリに実行されないです。(体感15分ズレている)
タイムアウトの設定
不測の事態で実行時間が伸びて課金されることを防ぐために、タイムアウトを設定しました。
おまけ
自動でcommitするために、yamlでユーザーを指定する必要があります。 勿論、自分のユーザー名とメールアドレスを使ってもいいのですが、GitHub Actions用のユーザーがあるっぽい?
以下のコメントによると、ユーザーは3種類あるみたいです🙂🙂🙂
初めてLT会を主催しました
概要
3月末にフィヨルドブートキャンプ(以下FBC)内で、「初めてのLT会vol.15(オンライン)」を主催しました⚡️
この記事は、
- 主催したきっかけ
- 主催としてやったこと
- 感想
を書きました✏️
主催したきっかけ
一言で言うと、「自分がLT登壇したかったから」です。
機会がないなら作るか!ということで、開催に至りました🏃♀️
LT会のテーマは「最近面白かったこと・もの」で、幅広く募集しました!
FBCでは、失敗を気にせずにどんどん挑戦してみればいいじゃん!という風土があると思います。 そのため、初めての主催でしたが抵抗を感じませんでした。
主催としてやったこと
先人達のTODOリストを参考に準備を進めていました。(FBCのDoc、先人のブログ記事など)
FBC内のLTで主催を考えている方は、これらのドキュメントを読めば間違いないと思います。
LT会当日までにやったことを時系列にまとめました。
| 時期 | 内容 |
|---|---|
| 1ヶ月前 | 告知 & 登壇者の募集 & 登壇者向けリハーサルの日程調整 |
| 1週間前 | リハーサル |
| LT会当日 | 司会業 |
あれもこれもやらないと、と焦っていたのですが、1つ1つのタスクはそこまで重くなかったです。
ふりかえり
事前準備で後悔したことがいくつかあるので、後学のためにブログに残しておきます。
1. 発表のコメントを事前に準備しておく
LT会当日は各登壇者の発表後に一言感想を伝えて進行をしていました。 リハーサルの時点で発表の内容がある程度わかっていたので、事前に準備して当日に備えておけばよかったです😫
場を繋ぐために私がその時思ったことを素直に発言していたのですが、失礼なこと言っていないか不安だったのと、沈黙を避けたい気持ちで焦りました💦 リハーサルの段階で準備していくことをお勧めします。
2. 主催者もLT登壇する場合、一時的に他の人に司会を変わってもらう
私のケースは特異かもしれないのですが、自分が発表する際には一時的に司会を変わってもらい、次の発表まで場を繋いでいただいた方がいいと思います。 余裕がなくなるのもあり、自分が一方的に話して置いてけぼりにしていないか不安になりました😅
感想
やりきったぞ〜という達成感でいっぱいです!
LT会終了後にフィードバック目的でアンケートを行ったのですが、どの回答もポジティブで励まされました🙌 また、LT登壇していただいた方にアンケートの回答を共有して喜んでもらえたのがとても嬉しかったです🥰
登壇者を募集した当時は、前回のLT会から1ヶ月しか空いておらず人数が集まるか不安でした。 無事人数も集まり、登壇した方にもチャレンジして良かったと言われて開催して本当に良かったです😭😭
改めてLT会に関わった全ての方に感謝いたします。 ありがとうございました!
エラー表示を可愛くするGemを作りました💎
概要
- Rubyプログラムを実行した際に発生したエラーをネコチャンが話すGemを作りました
- Gemを作った話をGotanda.rbでLTしました(ほぼ人生初LT)
Gemを作った
作ったGemはこれです。
Exception.prepend(MyModule)としてエラーメッセージを上書きしています💎
rubygems.org
本当は、エラーを日本語化する土台を先に作って、ネコチャンが話す拡張機能をつけるつもりでした。 (これは現在進行形で再設計しています。)
半年以上前から構想はあったのですが、興味があるものに色々手を出していて触れていませんでした😿
今回Gotanda.rbが開催されるとのことで、登壇駆動実装をしていました🏃♀️💨
ほぼ人生初のLT
フィヨルドブートキャンプのオンラインイベントではLTをしたので、ほぼ人生初と書きました。
オフラインで、しかも50人を超える人の前でLTをしたのでかなり緊張しました。
今回のLTはGemの実装を進めたいという理由以外に、けじめをつける(?)面もありました。
Gotanda.rbに初めて参加した際の懇親会で、勉強のためにGemを作っている話をしていましたが、今回のLTまでお披露目できていなかったのです。 これがずっと心残りだったのもあり、LTを決意しました。
スライドはこちらです。 speakerdeck.com
正直、GemのリポジトリはREADMEも書いていないし、commitの積み方も綺麗ではないです。
ですが、自分の中の"完璧"を待っていたらいつまでも来ないというのと、作って早めにフィードバック貰いたい気持ちの方が強かったです。
背伸びせず、ありのままを晒すことで、LTでも伸び伸びと話せたのは良かったと思います。緊張で震えてたけど発表中もリアクション貰えて楽しかったよ!
そして偶然にも私の発表直後にokuramasafumiさんがエラーを解決するために大事な話をされて、偶然にも話が繋がっていたのがとても印象的でしたw
LTというと、できるエンジニアの方が役に立つ話や仕事の学びを話している印象が強いと思いますが、私のようなへっぽこでも発表できているんだぞ!LT楽しいぞ!と誰かを勇気づけられたらとっても嬉しいな〜。
それにしてもリポジトリ見返すと伸び代ありまくりなのでなんとかしたい😐
LT後の反応
発表中のXのリアクションも沢山貰ったし(嬉しすぎて全ていいねした)、懇親会でも沢山声かけていただけてとっても嬉しかったです🥰
また、chobishibaさんのnoteで言及して貰ったのもすごく嬉しかったです!ありがとうございます〜!
最後に
私はお酒があまり強くないのですが、LTが終わった安堵感から会場で提供されていたビールを飲んでベロベロになりました🍺
空腹にビールを入れると二日酔いもするのでオススメしないです(戒め)
textlintを使って日報の校正を始めました
概要
- 文章を書くことに苦手意識があるので、textlintを導入してみました🔍
- 導入した話をLTで発表しました💬
- フィヨルドブートキャンプで開催されていた「初めてのLT会 vol.15⚡️」
- 人生初LTでしたが、満足のいく発表ができました🙌
発表スライド
この記事ではスライドに載せていない部分の、
- LTの動機
- 発表してよかったこと
などを書いていこうと思います✍️
発表の動機
主催者の方が声をかけてくれたのがきっかけです!
前々からLTが気になっていたのと、お誘いを受けたらなるべくYesと答えるのが私の信条なので、思い切って挑戦することにしました💪
(気持ち的には飲食店で大盛り無料と言われ、勿体無いから頼むのと変わらない気がする🤔)
何故そのネタを選んだの?
LT会のテーマは「私のおすすめ!」でした。
いくつか発表のネタはあったのですが、最終的にtextlintについて話すことにしました。
textlintにした決め手は、実際に使ってみて感動したのが一番大きいです。
自動で校正してくれるなんて、何て便利なんだ...。昔の自分に教えてあげたい🥺
発表本番に向けての準備
何よりも優先したのは、「時間内で発表が終わること」です。
発表時間は5〜10分と指定されていましたが、5分で収まるようにスライド作成に取り掛かりました。
(話が長いと聞いていて疲れてしまうので時間は死守しました。)
具体的にやったこと
- 紙上でスライドの大枠をざっと書きだしました。
- 上記を元に原稿なしで通しで話してみて、詰まった箇所をマークしていきました。
- 話しやすいように一文の文字数を減らし、言い切ることを意識して原稿を作成しました。
- 原稿を元にスライドの作成に着手しました。発表時間を見て枚数を調整しました。
- 原稿を見ながらスライド送りができるように発表練習をして、詰まらず話せる状態になったら原稿なしでスライドだけ見て話せるようにしました。
1~5までで2, 3日かかりました。
スライドのデザイン修正を含めると、完成するまでに4日かかりました。
発表した感想
初LTながら、堂々と発表できた気がします🙆♀️内心かなり緊張していましたが!w
発表後に皆さんからリアクション貰えるのがとても嬉しかったです!🙌
textlint便利そうなので使ってみたい!と仰った方もいて、発表した甲斐がありました😂
自分の好きな話を聞いて貰えるの嬉しいし楽しいですね〜!
この感動をまた味わいたくて、今月LT会を主催して発表することにしました🎉
LT会がなければ自分で発表する場を作っちゃえ〜!!ワハハ!!ってノリで主催したので頑張ります...!!
次回のLT会の感想もブログに残しますね〜。