11require 'rss'
22require 'open-uri'
33require 'yaml'
4+ require 'time'
45require 'active_support/broadcast_logger'
56
67namespace :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
62115end
0 commit comments