Skip to content

Commit 82d5305

Browse files
committed
CoderDojo RSSニュース管理システムの改善
レビューに対応: - 全置換方式→追記・更新方式に変更 - IDを古い順に連番で一意性確保 - published_at順ソートの実装 - IDをYAML出力の1行目に配置 主な変更点: • 既存記事のID保持: 新規追加時も既存IDは変更されない • 効率的な分類処理: 新規・更新・変更なしの3分類で最適化 • 詳細ログ出力: 新規X件、更新Y件を明確に表示 • 既存記事更新対応: タイトル・公開日変更の自動反映 • 安全性向上: Time.parseのrequire追加、エラーハンドリング強化 技術的改善: > 既存データをハッシュ化してO(1)検索に最適化 > 3つのアイテム分類(unchanged/updated/truly_new) > BroadcastLoggerでコンソール+ファイル出力 > formatted_itemsでYAML構造を整理
1 parent 71a1ab6 commit 82d5305

File tree

1 file changed

+63
-10
lines changed

1 file changed

+63
-10
lines changed

lib/tasks/fetch_news.rake

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'rss'
22
require 'open-uri'
33
require 'yaml'
4+
require 'time'
45
require 'active_support/broadcast_logger'
56

67
namespace :news do
@@ -13,6 +14,14 @@ namespace :news do
1314

1415
logger.info('==== START news:fetch ====')
1516

17+
# 既存の news.yml を読み込み
18+
yaml_path = Rails.root.join('db', 'news.yml')
19+
existing_news = if File.exist?(yaml_path)
20+
YAML.load_file(yaml_path)['news'] || []
21+
else
22+
[]
23+
end
24+
1625
# テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード
1726
feed_urls = if Rails.env.test? || Rails.env.staging?
1827
[Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s]
@@ -25,7 +34,7 @@ namespace :news do
2534
end
2635

2736
# RSS 取得&パース
28-
items = feed_urls.flat_map do |url|
37+
new_items = feed_urls.flat_map do |url|
2938
logger.info("Fetching RSS → #{url}")
3039
begin
3140
URI.open(url) do |rss|
@@ -44,19 +53,63 @@ namespace :news do
4453
end
4554
end
4655

47-
# 重複排除&日付降順ソート
48-
unique = items.uniq { |i| i['url'] }
49-
sorted = unique.sort_by { |i| i['published_at'] }.reverse
56+
# 既存データをハッシュに変換(URL をキーに)
57+
existing_items_hash = existing_news.index_by { |item| item['url'] }
5058

51-
# id を追加
52-
sorted.each { |i| i['id'] = i['url'] }
59+
# 新しいアイテムと既存アイテムを分離
60+
truly_new_items = []
61+
updated_items = []
62+
63+
new_items.each do |new_item|
64+
if existing_items_hash.key?(new_item['url'])
65+
# 既存アイテムの更新
66+
existing_item = existing_items_hash[new_item['url']]
67+
updated_item = existing_item.merge(new_item) # 新しい情報で更新
68+
updated_items << updated_item
69+
else
70+
# 完全に新しいアイテム
71+
truly_new_items << new_item
72+
end
73+
end
5374

54-
# YAML に書き出し
55-
File.open('db/news.yml', 'w') do |f|
56-
f.write({ 'news' => sorted }.to_yaml)
75+
# 既存の最大IDを取得
76+
max_existing_id = existing_news.map { |item| item['id'].to_i }.max || 0
77+
78+
# 新しいアイテムのみに ID を割り当て(古い順)
79+
truly_new_items_sorted = truly_new_items.sort_by { |item|
80+
Time.parse(item['published_at'])
81+
}
82+
83+
truly_new_items_sorted.each_with_index do |item, index|
84+
item['id'] = max_existing_id + index + 1
5785
end
5886

59-
logger.info("✅ Wrote #{sorted.size} items to db/news.yml")
87+
# 更新されなかった既存アイテムを取得
88+
updated_urls = updated_items.map { |item| item['url'] }
89+
unchanged_items = existing_news.reject { |item| updated_urls.include?(item['url']) }
90+
91+
# 全アイテムをマージ
92+
all_items = unchanged_items + updated_items + truly_new_items_sorted
93+
94+
# 日付降順ソート
95+
sorted_items = all_items.sort_by { |item|
96+
Time.parse(item['published_at'])
97+
}.reverse
98+
99+
File.open('db/news.yml', 'w') do |f|
100+
formatted_items = sorted_items.map do |item|
101+
{
102+
'id' => item['id'],
103+
'url' => item['url'],
104+
'title' => item['title'],
105+
'published_at' => item['published_at']
106+
}
107+
end
108+
109+
f.write({ 'news' => formatted_items }.to_yaml)
110+
end
111+
112+
logger.info("✅ Wrote #{sorted_items.size} items to db/news.yml (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)")
60113
logger.info('==== END news:fetch ====')
61114
end
62115
end

0 commit comments

Comments
 (0)