Vol.042026年5月9日
Logged2 min · SoraEndo
Process

コンテンツ ID 管理を seed と admin で二重化しない

seed-fu には骨格データだけ、記事は admin / Bot API のみ。db_reset で消えない構造で運用する境界の引き方。

SoSoraEndo2026年5月9日2 min736

seed と admin の二重管理問題

Rails + 任意 frontend で個人ブログを組むとき、最初に詰まるのが 「初期記事をどこに入れるか」

選択肢:

  1. seed-fu — DB に直接 fixture で投入。コードと一緒にバージョン管理される
  2. admin で手入力 — 公開後に編集者が書く。DB の中だけに存在する
  3. 両方使う — seed で雛形、admin で本物に書き換える

私は最初 (3) で運用していて、seed と admin の両方が真実 という二重管理状態に陥った。

二重管理の何が困るか

具体的な症状:

  • make db_reset を流すと admin で書いた記事が消える
  • seed-fu の fixture と DB の状態がズレる
  • 本番環境に dev で書いた記事が乗らない / 逆もしかり
  • 「この記事は seed?admin?」が判別不能

新規プロジェクトの最初の数週間で、必ずと言っていいほど踏む地雷。

解決方針: seed は「最小骨格」だけ

二重管理を避けるルール:

  • seed-fu には骨格データ(admin / authors / categories / tags / api_keys)だけ入れる
  • 記事は seed に入れない(demo 用の数件は OK、ただし本番では skip)
  • public 記事は admin から書く
# backend/db/fixtures/03_admin_users.rb  ← seed
# backend/db/fixtures/04_authors.rb       ← seed
# backend/db/fixtures/05_categories.rb    ← seed
# backend/db/fixtures/06_tags.rb          ← seed
# backend/db/fixtures/09_api_keys.rb      ← seed
# 記事は admin or Bot API のみ

これで make db_reset で消えるのは「常に再投入される骨格」だけ。記事は admin に書いたら DB に残り続ける。

demo 記事を入れたい時

最初の見栄えのために demo 記事を 5〜10 件入れたい時もある。これも seed-fu でやるが、環境変数で skip できるようにする:

# backend/db/fixtures/07_posts.rb
return if ENV['SKIP_DEMO_POSTS'] == 'true'

posts = [
  { slug: 'welcome-to-aether-echoes', ... }
]
posts.each { |p| Post.seed(:slug) { |s| s.assign_attributes(p) } }

本番デプロイ時に SKIP_DEMO_POSTS=true を渡せば、demo は入らない。

Bot API は draft 強制

オリジナル記事を増やしていくフェーズでは、Bot API 経由で draft 状態で投入 → admin で校正 → publish の運用が安定する。

# Bot::PostUpsertService
def call
  payload = @payload.dup
  payload[:status] = 'draft'  # Bot 経由は強制 draft
  Post::UpsertService.new(payload: payload, admin_user: @api_key.created_by).call
end

Bot で投入された記事も draft なので、db_reset 影響を受けない(もとから「admin で公開する」運用)。

本番デプロイ時の注意

dev で書いた記事を本番に持っていきたい時、選択肢は 2 つ:

  1. mysqldump → import — 単純、確実だが手作業
  2. API 経由で再投稿 — 自動化できるが冪等性に注意

個人サイト規模なら mysqldump で十分。月に 1 度、dev DB の posts テーブルだけ dump して本番に入れる、という運用で動く。

docker compose exec backend mysqldump -u root -proot \
  --no-create-info --skip-extended-insert app_dev posts > /tmp/posts.sql

--no-create-info でテーブル定義は出さず、--skip-extended-insert で 1 行 1 INSERT に。差分管理しやすい形にする。

まとめ

content ID 管理の境界:

  • seed-fu: admin / authors / categories / tags / api_keys / 必要なら demo 記事
  • admin / Bot API: public 記事(draft → 校正 → published)
  • db_reset: seed のみが再投入、記事は別管理

この境界を初日に引くと、二重管理の罠は避けられる。

真実の置き場所を 1 つに決める。これがコンテンツ運用の出発点。

Tags

Reaction

Share

X (Twitter)