ブログ

画像を1つのモデルに統一した理由 — NARA NEXT開発ログ④

ブログ

こんにちは、かつコードです。

NARA NEXTの開発がある程度進んだ段階で、実際にフロント画面を見返していて、ある弱点に気づきました。観光地の詳細ページで、タイトルと本文の間に画像が一切なく、テキストだけがずらっと並んでいたのです。地味ですが、これは訴求力にかなり影響する問題でした。

画像管理を1つのMediaモデルに統一すると決めた

結論:スポット・店舗・エリア・旅行者のチェックイン写真——用途がバラバラな画像を、すべて1つのMediaテーブルで管理する設計にしました。

「誰が使うか」がバラバラな画像たち

NARA NEXTで画像を使う場面を整理すると、次のようにアップロードする人も目的も異なることが分かりました。

  • 観光地のメイン写真・スタンプ画像 → 基本的に運営者(管理者)が登録
  • 店舗のメイン写真 → 店舗オーナー自身がアップロードすることもある
  • 旅行者のチェックイン記念写真 → 旅行者本人が、自分の記録として残す

これらを個別のテーブル・個別のカラムで作ってしまうと、モデルが増えるたびに似たようなアップロード処理を何度も書くことになります。過去のプロジェクトでも、Mediaモデル1つで画像を一元管理する設計を採用した経験があり、今回もその考え方を踏襲しました。

ポリモーフィックリレーションで「どのモデルの画像か」を表現する

mediaテーブルは、mediable_type / mediable_idという2つのカラムで「この画像がどのモデルに属するか」を表現します(Eloquentのポリモーフィックリレーション)。これはNARA NEXTのアプリ内で既に使っていたstopable_type/likeable_typeという設計パターンと同じ考え方で、新しい概念を持ち込まずに済みました。

さらに、「誰がアップロードしたか」を表すuploader_type / uploader_idも同じ仕組みで持たせています。管理者(User)、店舗オーナー(ShopOwner)、旅行者(Traveler)のどれであっても、同じ1つのカラムの組み合わせで表現できます。

そして、1つのモデルが複数種類の画像を持てるように、collectionというタグ(hero=メイン画像、stamp=スタンプ画像、photo=チェックイン写真、など)を用意しました。将来1つの観光地に複数枚のギャラリー画像を持たせたくなっても、テーブル構造を変えずに対応できます。

プライバシーは「構造」で守る

結論:旅行者のチェックイン写真は、is_publicというフラグだけでなく、モデルの参照関係そのものによって公開ページから漏れない構造にしています。

これは開発中に見つけた、地味ですが重要な設計上の反省点です。旅行者のチェックイン写真は「絶対に公開ページに出さない」という前提で作られていましたが、実際にはその前提を守る仕組みが明示的には存在していませんでした。もし今後別の開発者がコードを書き足したときに、うっかり公開用のAPIレスポンスに紛れ込んでしまう可能性が構造的に残っていたのです。

Mediaモデルへの統一に合わせて、この点を作り直しました。

  • チェックイン写真はCheckInというモデルに紐づくMediaレコードとして保存され、is_publicはfalseに設定される
  • 観光地・店舗・エリアの公開APIは、それぞれ自分自身のheroImageやstampImageというリレーションしか参照しない(CheckInのレコードを直接参照する経路がそもそも存在しない)

つまり、フラグ1つに頼るのではなく、「そもそも参照する経路がない」という構造上の保証を重ねることで、うっかりミスが起きにくい形にしました。

画像圧縮ツールへの案内で、自社サービスとも連携させた

結論:Mediaモデルの実装に合わせて、店舗オーナーが画像をアップロードする画面に、自社の画像圧縮ツール「katsuooool」への案内リンクを添えました。

店舗オーナーがつまずきやすいポイント

店舗オーナーが自分のスマートフォンで撮った写真をそのままアップロードしようとすると、ファイルサイズが大きすぎてアップロードに失敗することがよくあります。NARA NEXTでは画像サイズの上限を5MBに設定していますが、ただエラーメッセージを出すだけでは不親切です。

そこで、アップロードエラーのメッセージ自体に、「katsuooool(katsuooool)で圧縮・切り取りしてから、もう一度お試しください」という案内を含めました。katsuoooolは無料で使える画像圧縮ツールなので、店舗オーナーにとって試すハードルが低く、結果として自社ツールの利用機会を増やすことにもつながります。無理な売り込みではなく、「困っている人に、実際に役立つ無料ツールを案内する」という自然な連携です。

まとめ

画像管理をMediaモデル1つに統一したことで、観光地・店舗・エリア・チェックイン写真という性質の異なる画像を、同じ仕組みで扱えるようになりました。「誰の画像か」「何のための画像か」をポリモーフィックリレーションとcollectionタグで表現し、プライバシーが必要な画像は構造そのもので守る。そして副産物として、自社の画像圧縮ツールとの連携も生まれました。地味な裏側の設計ですが、フロント画面の訴求力に直結する、今回の開発の中でも印象に残っている改修です。

次回は、視点を変えて「儲けない」ことをどう設計に落とし込んだか、収益化方針の話をお伝えします。

今日からできる3つのアクション

  • 自分のサービスで「似た機能なのに別々に実装している」箇所がないか、ポリモーフィックな設計で統一できないか考えてみる
  • 「フラグだけに頼っている」プライバシー保護の仕組みがないか、参照経路そのものを見直してみる
  • エラーメッセージの中に、ユーザーが実際に助かる案内(無料ツールなど)を添えられないか検討してみる

このシリーズの他の回もあわせてご覧ください(着想編、技術選定編、コンテンツ信頼性編、収益化編、コンテンツ拡充編、まとめ編)。


実際に使ってみる → NARA NEXT / 画像圧縮ツール → katsuooool