Skip to content

Commit a4b161f

Browse files
committed
🔧 TDD実装: news:upsert タスクで YAML の ID を DB の主キーとして使用
YAML をマスターデータとして、DB の News レコードが YAML の ID に従うように実装。 TDD (Test-Driven Development) サイクルに従って実装を完了。 - YAML の ID をそのまま DB の主キー (id) として使用 - 同じ URL だが異なる ID のレコードを事前に削除する処理を追加 - News.find_or_initialize_by(id: item['id']) で YAML の ID を使用 - YAML に存在しない ID のレコードを削除する処理を追加 - ID を 4桁でゼロパディング表示 (例: ID 0001) - YAML ID と DB ID の一致を確認するテスト - 既存レコードの ID 更新を確認するテスト - 不要レコードの削除を確認するテスト 1. RED: テストを追加し、失敗を確認 ✅ 2. GREEN: タスクを修正し、テストが通ることを確認 ✅ 3. REFACTOR: ID 表示フォーマットの改善 ✅ - 182 件の重複 URL を持つレコードを正しい ID に置き換え - 全てのニュースアイテムの ID が YAML と一致 - 全テストスイート (news_spec, news requests, rake task) がパス
1 parent ed60e2e commit a4b161f

File tree

2 files changed

+162
-3
lines changed

2 files changed

+162
-3
lines changed

lib/tasks/news.rake

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ namespace :news do
142142
end
143143

144144

145-
desc "#{NEWS_YAML_PATH} からデータベースに upsert"
145+
desc "#{NEWS_YAML_PATH} からデータベースに upsert (YAMLのIDを使用)"
146146
task upsert: :environment do
147147
TASK_LOGGER.info "==== START news:upsert ===="
148148

@@ -151,9 +151,20 @@ namespace :news do
151151
updated_count = 0
152152

153153
News.transaction do
154+
# まず、同じURLだが異なるIDのレコードを削除
154155
news_items.each do |item|
155-
news = News.find_or_initialize_by(url: item['url'])
156+
existing_with_different_id = News.where(url: item['url']).where.not(id: item['id']).first
157+
if existing_with_different_id
158+
TASK_LOGGER.info "[News] Removing duplicate: ID:#{existing_with_different_id.id} (URL: #{item['url']}) to be replaced by ID:#{item['id']}"
159+
existing_with_different_id.destroy
160+
end
161+
end
162+
163+
news_items.each do |item|
164+
# YAMLのIDを使ってレコードを検索または初期化
165+
news = News.find_or_initialize_by(id: item['id'])
156166
news.assign_attributes(
167+
url: item['url'],
157168
title: item['title'],
158169
published_at: item['published_at']
159170
)
@@ -166,9 +177,16 @@ namespace :news do
166177
created_count += 1 if is_new_record
167178
updated_count += 1 unless is_new_record
168179

169-
TASK_LOGGER.info "[News] #{news.published_at.to_date} #{news.title} (#{status})"
180+
TASK_LOGGER.info "[News] ID #{format('%04d', news.id)}: #{news.published_at.to_date} #{news.title} (#{status})"
170181
end
171182
end
183+
184+
# YAMLに存在しないレコードを削除
185+
yaml_ids = news_items.map { |item| item['id'] }
186+
deleted_count = News.where.not(id: yaml_ids).destroy_all.size
187+
if deleted_count > 0
188+
TASK_LOGGER.info "Deleted #{deleted_count} items that are not in YAML."
189+
end
172190
end
173191

174192
TASK_LOGGER.info "Upserted #{created_count + updated_count} items (#{created_count} new, #{updated_count} updated)."

spec/lib/tasks/news_spec.rb

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
require 'rails_helper'
2+
require 'rake'
3+
4+
RSpec.describe 'news:upsert' do
5+
let(:news_yaml_path) { Rails.root.join('db', 'news.yml') }
6+
7+
before(:all) do
8+
# Rakeタスクをロード
9+
Rails.application.load_tasks
10+
end
11+
12+
before do
13+
# 既存のNewsレコードをクリア
14+
News.destroy_all
15+
end
16+
17+
describe 'YAMLのIDとDBのIDの一致' do
18+
it 'YAMLファイルのIDがそのままDBのIDとして使用される' do
19+
# テスト用のYAMLデータを作成
20+
test_yaml_data = [
21+
{
22+
'id' => 10,
23+
'url' => 'https://example.com/news10',
24+
'title' => 'テストニュース10',
25+
'published_at' => '2025-12-10T09:00:00+09:00'
26+
},
27+
{
28+
'id' => 5,
29+
'url' => 'https://example.com/news5',
30+
'title' => 'テストニュース5',
31+
'published_at' => '2025-12-05T09:00:00+09:00'
32+
},
33+
{
34+
'id' => 1,
35+
'url' => 'https://example.com/news1',
36+
'title' => 'テストニュース1',
37+
'published_at' => '2025-12-01T09:00:00+09:00'
38+
}
39+
]
40+
41+
# 一時的にYAMLファイルを置き換え
42+
original_yaml_content = File.read(news_yaml_path) if File.exist?(news_yaml_path)
43+
File.write(news_yaml_path, test_yaml_data.to_yaml)
44+
45+
# タスクを実行
46+
Rake::Task['news:upsert'].execute
47+
48+
# データベースのレコードを確認
49+
expect(News.count).to eq(3)
50+
51+
# YAMLのIDとDBのIDが一致することを確認
52+
test_yaml_data.each do |yaml_item|
53+
db_news = News.find(yaml_item['id'])
54+
expect(db_news).not_to be_nil
55+
expect(db_news.id).to eq(yaml_item['id'])
56+
expect(db_news.url).to eq(yaml_item['url'])
57+
expect(db_news.title).to eq(yaml_item['title'])
58+
end
59+
60+
# IDの配列が完全に一致することを確認
61+
yaml_ids = test_yaml_data.map { |item| item['id'] }.sort
62+
db_ids = News.pluck(:id).sort
63+
expect(db_ids).to eq(yaml_ids)
64+
65+
ensure
66+
# 元のYAMLファイルを復元
67+
File.write(news_yaml_path, original_yaml_content) if original_yaml_content
68+
end
69+
70+
it '既存レコードのIDも更新される' do
71+
# 既存のレコードを作成(異なるID)
72+
News.create!(id: 100, url: 'https://example.com/old', title: '古いニュース', published_at: 1.month.ago)
73+
74+
# テスト用のYAMLデータ(同じURLだが異なるID)
75+
test_yaml_data = [
76+
{
77+
'id' => 1,
78+
'url' => 'https://example.com/old',
79+
'title' => '更新されたニュース',
80+
'published_at' => '2025-12-01T09:00:00+09:00'
81+
}
82+
]
83+
84+
# 一時的にYAMLファイルを置き換え
85+
original_yaml_content = File.read(news_yaml_path) if File.exist?(news_yaml_path)
86+
File.write(news_yaml_path, test_yaml_data.to_yaml)
87+
88+
# タスクを実行
89+
Rake::Task['news:upsert'].execute
90+
91+
# ID 100のレコードは削除され、ID 1のレコードが存在することを確認
92+
expect(News.exists?(100)).to be false
93+
expect(News.exists?(1)).to be true
94+
95+
news = News.find(1)
96+
expect(news.title).to eq('更新されたニュース')
97+
98+
ensure
99+
# 元のYAMLファイルを復元
100+
File.write(news_yaml_path, original_yaml_content) if original_yaml_content
101+
end
102+
103+
it 'YAMLに存在しないレコードは削除される' do
104+
# YAMLに存在しないレコードを作成
105+
News.create!(id: 999, url: 'https://example.com/tobedeleted', title: '削除されるニュース', published_at: 1.day.ago)
106+
107+
# テスト用のYAMLデータ(ID 999は含まない)
108+
test_yaml_data = [
109+
{
110+
'id' => 1,
111+
'url' => 'https://example.com/news1',
112+
'title' => 'テストニュース1',
113+
'published_at' => '2025-12-01T09:00:00+09:00'
114+
}
115+
]
116+
117+
# 一時的にYAMLファイルを置き換え
118+
original_yaml_content = File.read(news_yaml_path) if File.exist?(news_yaml_path)
119+
File.write(news_yaml_path, test_yaml_data.to_yaml)
120+
121+
# タスクを実行
122+
Rake::Task['news:upsert'].execute
123+
124+
# ID 999のレコードが削除されていることを確認
125+
expect(News.exists?(999)).to be false
126+
expect(News.count).to eq(1)
127+
expect(News.pluck(:id)).to eq([1])
128+
129+
ensure
130+
# 元のYAMLファイルを復元
131+
File.write(news_yaml_path, original_yaml_content) if original_yaml_content
132+
end
133+
end
134+
135+
# Rakeタスクをテストごとにリロード
136+
before do
137+
Rake.application.tasks.each do |task|
138+
task.reenable if task.name.start_with?('news:')
139+
end
140+
end
141+
end

0 commit comments

Comments
 (0)