1
1
require 'rss'
2
2
require 'open-uri'
3
3
require 'yaml'
4
+ require 'time'
4
5
require 'active_support/broadcast_logger'
5
6
6
7
namespace :news do
@@ -13,6 +14,14 @@ namespace :news do
13
14
14
15
logger . info ( '==== START news:fetch ====' )
15
16
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
+
16
25
# テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード
17
26
feed_urls = if Rails . env . test? || Rails . env . staging?
18
27
[ Rails . root . join ( 'spec' , 'fixtures' , 'sample_news.rss' ) . to_s ]
@@ -25,7 +34,7 @@ namespace :news do
25
34
end
26
35
27
36
# RSS 取得&パース
28
- items = feed_urls . flat_map do |url |
37
+ new_items = feed_urls . flat_map do |url |
29
38
logger . info ( "Fetching RSS → #{ url } " )
30
39
begin
31
40
URI . open ( url ) do |rss |
@@ -44,19 +53,63 @@ namespace :news do
44
53
end
45
54
end
46
55
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' ] }
50
58
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
53
74
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
57
85
end
58
86
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)" )
60
113
logger . info ( '==== END news:fetch ====' )
61
114
end
62
115
end
0 commit comments