From 1d870a4a08e9b0df99ac7904adcdbe30f4b673aa Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 26 Jun 2025 11:03:00 +0900 Subject: [PATCH 01/31] =?UTF-8?q?feat:=20RSS=E3=83=95=E3=82=A3=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - に タスクを実装 - CoderDojo.jp の RSS フィードから記事タイトル、公開日、URL を取得して出力 - URL を ID として重複排除を実装 - をテスト用フィクスチャとして追加 --- lib/tasks/fetch_news.rake | 62 +++++++++++++++++++++++++++++++++++ spec/fixtures/sample_news.rss | 23 +++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 lib/tasks/fetch_news.rake create mode 100644 spec/fixtures/sample_news.rss diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake new file mode 100644 index 000000000..b53ed1734 --- /dev/null +++ b/lib/tasks/fetch_news.rake @@ -0,0 +1,62 @@ +require 'rss' +require 'open-uri' +require 'yaml' +require 'active_support/broadcast_logger' + +namespace :news do + desc 'RSS フィードから最新ニュースを取得し、db/news.yml に書き出す' + task fetch: :environment do + # ロガー設定(ファイル+コンソール出力) + file_logger = ActiveSupport::Logger.new('log/news.log') + console = ActiveSupport::Logger.new(STDOUT) + logger = ActiveSupport::BroadcastLogger.new(file_logger, console) + + logger.info('==== START news:fetch ====') + + # テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード + feed_urls = if Rails.env.test? || Rails.env.staging? + [ Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s ] + else + [ + 'https://coderdojo.jp/feed', + # 必要に応じて他 Dojo の RSS もここに追加可能 + # 'https://coderdojotokyo.org/feed', + ] + end + + # RSS 取得&パース + items = feed_urls.flat_map do |url| + logger.info("Fetching RSS → #{url}") + begin + URI.open(url) do |rss| + feed = RSS::Parser.parse(rss, false) + feed.items.map do |item| + { + 'url' => item.link, + 'title' => item.title, + 'published_at' => item.pubDate.to_s + } + end + end + rescue => e + logger.warn("⚠️ Failed to fetch #{url}: #{e.message}") + [] + end + end + + # 重複排除&日付降順ソート + unique = items.uniq { |i| i['url'] } + sorted = unique.sort_by { |i| i['published_at'] }.reverse + + # id を追加 + sorted.each { |i| i['id'] = i['url'] } + + # YAML に書き出し + File.open('db/news.yml', 'w') do |f| + f.write({ 'news' => sorted }.to_yaml) + end + + logger.info("✅ Wrote #{sorted.size} items to db/news.yml") + logger.info('==== END news:fetch ====') + end +end diff --git a/spec/fixtures/sample_news.rss b/spec/fixtures/sample_news.rss new file mode 100644 index 000000000..2e31f4c71 --- /dev/null +++ b/spec/fixtures/sample_news.rss @@ -0,0 +1,23 @@ + + + + Sample News Feed + https://coderdojo.jp/ + テスト用のサンプルニュースフィード + + + テスト記事① + https://example.com/articles/1 + Mon, 01 Jun 2025 10:00:00 +0900 + サンプル記事の本文① + + + + テスト記事② + https://example.com/articles/2 + Tue, 02 Jun 2025 11:30:00 +0900 + サンプル記事の本文② + + + + From d6e64e5abeabe5362a486ce6f952632c8d87c7ea Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 26 Jun 2025 14:13:05 +0900 Subject: [PATCH 02/31] =?UTF-8?q?ci:=20news:fetch=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=82=92=E6=AF=8E=E6=9C=9D9:00=20JST=E3=81=AB?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E3=81=99=E3=82=8B=E3=83=AF=E3=83=BC=E3=82=AF?= =?UTF-8?q?=E3=83=95=E3=83=AD=E3=83=BC=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .github/workflows/fetch_news.yml を作成 - cron('0 0 * * *') で毎朝 9:00 JST に自動実行を設定 - workflow_dispatch による手動トリガーを有効化 - ruby/setup-ruby で から Ruby をセットアップ+bundler-cache を有効化 - news:fetch タスク実行後に db/news.yml の差分をコミット&プッシュする処理を追加 --- .github/workflows/fetch_news.yml | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .github/workflows/fetch_news.yml diff --git a/.github/workflows/fetch_news.yml b/.github/workflows/fetch_news.yml new file mode 100644 index 000000000..9b54c3f3c --- /dev/null +++ b/.github/workflows/fetch_news.yml @@ -0,0 +1,39 @@ +name: Fetch News + +on: + schedule: + # 毎朝 9:00 JST + - cron: '0 0 * * *' + workflow_dispatch: + +jobs: + fetch: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version-file: .ruby-version + bundler-cache: true + + - name: Install dependencies + run: bundle install --jobs 4 --retry 3 + + - name: Run news:fetch task + run: bin/rails news:fetch + + - name: Commit updated news.yml + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add db/news.yml + if ! git diff --cached --quiet; then + git commit -m "chore: update news.yml via GitHub Actions" + git push + else + echo "No changes in db/news.yml" + fi From 35004226c1071c559fb9bc45b0e847504a650db0 Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 30 Jun 2025 13:19:55 +0900 Subject: [PATCH 03/31] =?UTF-8?q?News=E3=83=A2=E3=83=87=E3=83=AB=E3=81=A8?= =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=82=B0=E3=83=AC=E3=83=BC=E3=82=B7=E3=83=A7?= =?UTF-8?q?=E3=83=B3=E3=80=81=E3=83=86=E3=82=B9=E3=83=88=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - title:string、url:string、published_at:datetime の Newsモデルを生成 - url カラムにユニークインデックスを追加し重複登録を防止 - spec/models/news_spec.rb にモデルスペック用ファイルを作成 - spec/factories/news.rb に FactoryBot ファクトリを追加 --- app/models/news.rb | 2 ++ db/migrate/20250630040611_create_news.rb | 13 +++++++++++++ db/schema.rb | 13 +++++++++++-- spec/factories/news.rb | 7 +++++++ spec/models/news_spec.rb | 5 +++++ 5 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 app/models/news.rb create mode 100644 db/migrate/20250630040611_create_news.rb create mode 100644 spec/factories/news.rb create mode 100644 spec/models/news_spec.rb diff --git a/app/models/news.rb b/app/models/news.rb new file mode 100644 index 000000000..193f62c49 --- /dev/null +++ b/app/models/news.rb @@ -0,0 +1,2 @@ +class News < ApplicationRecord +end diff --git a/db/migrate/20250630040611_create_news.rb b/db/migrate/20250630040611_create_news.rb new file mode 100644 index 000000000..04273478c --- /dev/null +++ b/db/migrate/20250630040611_create_news.rb @@ -0,0 +1,13 @@ +class CreateNews < ActiveRecord::Migration[8.0] + def change + create_table :news do |t| + t.string :title + t.string :url + t.datetime :published_at + + t.timestamps + end + + add_index :news, :url, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 00d9982f0..af544a199 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,10 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_05_20_091834) do +ActiveRecord::Schema[8.0].define(version: 2025_06_30_040611) do # These are extensions that must be enabled in order to support this database + enable_extension "pg_catalog.plpgsql" enable_extension "pg_stat_statements" - enable_extension "plpgsql" create_table "dojo_event_services", id: :serial, force: :cascade do |t| t.integer "dojo_id", null: false @@ -58,6 +58,15 @@ t.index ["service_name", "event_id"], name: "index_event_histories_on_service_name_and_event_id", unique: true end + create_table "news", force: :cascade do |t| + t.string "title" + t.string "url" + t.datetime "published_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["url"], name: "index_news_on_url", unique: true + end + create_table "podcasts", force: :cascade do |t| t.string "enclosure_url", null: false t.string "title", null: false diff --git a/spec/factories/news.rb b/spec/factories/news.rb new file mode 100644 index 000000000..4bfe4e543 --- /dev/null +++ b/spec/factories/news.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :news do + title { "MyString" } + url { "MyString" } + published_at { "2025-06-30 13:06:11" } + end +end diff --git a/spec/models/news_spec.rb b/spec/models/news_spec.rb new file mode 100644 index 000000000..0063aaf42 --- /dev/null +++ b/spec/models/news_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe News, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end From 6e9c6afd120f1a741b1de81faafd7eb9e0b2e6ff Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 1 Jul 2025 09:51:43 +0900 Subject: [PATCH 04/31] =?UTF-8?q?=E3=83=8B=E3=83=A5=E3=83=BC=E3=82=B9=20YA?= =?UTF-8?q?ML=E2=86=92DB=20=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=BF=E3=82=B9=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0=EF=BC=86?= =?UTF-8?q?news.yml=20=E3=82=92=E5=88=9D=E6=9C=9F=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/tasks/import_news.rake を作成し、db/news.yml の内容を News テーブルに upsert するタスクを実装 - db/news.yml を空の配列形式()で初期化 --- db/news.yml | 2 ++ lib/tasks/import_news.rake | 24 ++++++++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 db/news.yml create mode 100644 lib/tasks/import_news.rake diff --git a/db/news.yml b/db/news.yml new file mode 100644 index 000000000..270438064 --- /dev/null +++ b/db/news.yml @@ -0,0 +1,2 @@ +--- +news: [] diff --git a/lib/tasks/import_news.rake b/lib/tasks/import_news.rake new file mode 100644 index 000000000..ed4f589a7 --- /dev/null +++ b/lib/tasks/import_news.rake @@ -0,0 +1,24 @@ +require 'yaml' + +namespace :news do + desc "db/news.yml を読み込んで News テーブルを upsert する" + task import_from_yaml: :environment do + yaml_path = Rails.root.join('db', 'news.yml') + raw = YAML.load_file(yaml_path) + + # entries を計算 + entries = raw['news'] || [] + + entries.each do |attrs| + news = News.find_or_initialize_by(url: attrs['url']) + news.assign_attributes( + title: attrs['title'], + published_at: attrs['published_at'] + ) + news.save! + puts "[news] #{news.published_at.to_date} #{news.title}" + end + + puts "Imported #{entries.size} items." + end +end From 55d89c71b22656ebdcf1640fec9bd57509adc79a Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 1 Jul 2025 11:28:41 +0900 Subject: [PATCH 05/31] =?UTF-8?q?Release=20Phase=E3=81=AB=20=E3=83=8B?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=82=B9=E3=83=9D=E3=82=A4=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script/release.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/script/release.sh b/script/release.sh index bea511eb8..9c303d2d7 100755 --- a/script/release.sh +++ b/script/release.sh @@ -3,5 +3,6 @@ set -e bundle exec rails db:migrate bundle exec rails db:seed bundle exec rails dojos:update_db_by_yaml +bundle exec rails news:import_from_yaml bundle exec rails dojo_event_services:upsert bundle exec rails podcasts:upsert From d0f3511ed1d6b3bf1114ef0297d222d3eeb97bb4 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 2 Jul 2025 08:32:04 +0900 Subject: [PATCH 06/31] =?UTF-8?q?fetch=5Fnews=E3=82=BF=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89URL=E3=82=92=20htt?= =?UTF-8?q?ps://coderdojo.jp/#news=20=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/fetch_news.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index b53ed1734..a2ded6ff4 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -18,7 +18,7 @@ namespace :news do [ Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s ] else [ - 'https://coderdojo.jp/feed', + 'https://coderdojo.jp/#news', # 必要に応じて他 Dojo の RSS もここに追加可能 # 'https://coderdojotokyo.org/feed', ] From e1ab5700373177264c634793448b34dab6bcf7bc Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 3 Jul 2025 09:00:00 +0900 Subject: [PATCH 07/31] =?UTF-8?q?feat:fetch=5Fnews=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E3=82=AF=E3=81=AE=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89URL?= =?UTF-8?q?=E3=82=92=20https://news.coderdojo.jp/feed/=20=E3=81=AB?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/fetch_news.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index a2ded6ff4..481aeb5f5 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -18,7 +18,7 @@ namespace :news do [ Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s ] else [ - 'https://coderdojo.jp/#news', + 'https://news.coderdojo.jp/feed/', # 必要に応じて他 Dojo の RSS もここに追加可能 # 'https://coderdojotokyo.org/feed', ] From 986d4adb29ea930b540c064c80bb35d5a50943b8 Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 7 Jul 2025 09:02:06 +0900 Subject: [PATCH 08/31] =?UTF-8?q?feat:=20=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=BB=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92?= =?UTF-8?q?DB=E3=81=8B=E3=82=89=E3=81=AE=E5=8B=95=E7=9A=84=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=AB=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - home_controller.rb: - ニュースデータをDBから取得し、@news_itemsに格納するロジックを追加。 - 最新7件を公開日降順で表示する。 - show.html.erb: - ニュース表示部分をハードコードから@news_itemsを使った動的表示に変更。 - タイトルに既存の絵文字がない場合は「📰」を自動付与するロジックを実装。 - 各ニュースのリンク先をDBから取得したnews.urlに設定。 - news.yml: - fetchタスクによって更新された最新のニュースデータを含む。 --- app/controllers/home_controller.rb | 1 + app/views/home/show.html.erb | 33 +++++++++-------------- db/news.yml | 42 +++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 22 deletions(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index e9d05b21c..ab3cedc69 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -3,5 +3,6 @@ def show @dojo_count = Dojo.active_dojos_count @regions_and_dojos = Dojo.group_by_region_on_active @prefectures_and_dojos = Dojo.group_by_prefecture_on_active + @news_items = News.all.order(published_at: :desc).limit(7) end end diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb index 79910fe7d..deca2a61c 100644 --- a/app/views/home/show.html.erb +++ b/app/views/home/show.html.erb @@ -177,27 +177,18 @@
    -
  • 💻 - <%= link_to '米国系IT企業からCoderDojoへ、55台のノートPC寄贈', news_url('/2025/04/04/55-laptops-to-coderdojo/') %> -
  • -
  • 📰 - <%= link_to 'DojoLetter Vol.81 2025年01月号', news_url('/2025/03/10/dojoletter-vol-82-2025年01月号/') %> -
  • -
  • 🎲 - <%= link_to 'ダイス×プログラミング『ニャイス!コード』 を75台寄贈', news_url('/2025/02/14/coderdojo-de-nyaicecode/') %> -
  • -
  • 📰 - <%= link_to 'DojoLetter Vol.80 2024年12月号', news_url('/2025/02/10/dojoletter-vol-80-2024年12月号/') %> -
  • -
  • 📰 - <%= link_to 'DojoLetter Vol.79 2024年11月号', news_url('/2025/01/14/dojoletter-vol-79-2024年11月号/') %> -
  • -
  • 🎄 - <%= link_to '128台のノートPC寄贈、Box JapanからCoderDojoへ', news_url('/2024/12/25/box-japan-to-coderdojo/') %> -
  • -
  • 📰 - <%= link_to 'DojoLetter Vol.78 2024年10月号', news_url('/2024/12/10/dojoletter-vol-78-2024年10月号/') %> -
  • + <% @news_items.each do |news| %> +
  • + <% has_emoji = news.title.chars.any? { |char| char.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) } %> + <% if has_emoji %> + <% # 絵文字が含まれていれば、タイトルをそのまま表示 %> + <%= link_to news.title, news.url, target: '_blank', rel: 'noopener noreferrer' %> + <% else %> + <% # 絵文字が含まれていなければ、「📰」をつけて表示 %> + 📰 <%= link_to news.title, news.url, target: '_blank', rel: 'noopener noreferrer' %> + <% end %> +
  • + <% end %>

最新情報はメールで受け取れます。 diff --git a/db/news.yml b/db/news.yml index 270438064..1b15bb587 100644 --- a/db/news.yml +++ b/db/news.yml @@ -1,2 +1,42 @@ --- -news: [] +news: +- url: https://news.coderdojo.jp/2024/12/25/box-japan-to-coderdojo/ + title: "\U0001F384128台のノート PC 寄贈、Box Japan から CoderDojo へ \U0001F381" + published_at: Wed, 25 Dec 2024 11:00:40 +0000 + id: https://news.coderdojo.jp/2024/12/25/box-japan-to-coderdojo/ +- url: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.79 2024年11月号 + published_at: Tue, 14 Jan 2025 03:30:45 +0000 + id: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.85 2025年04月号 + published_at: Tue, 10 Jun 2025 03:30:18 +0000 + id: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2024/12/10/dojoletter-vol-78-2024%e5%b9%b410%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.78 2024年10月号 + published_at: Tue, 10 Dec 2024 04:00:06 +0000 + id: https://news.coderdojo.jp/2024/12/10/dojoletter-vol-78-2024%e5%b9%b410%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.83 2025年02月号 + published_at: Thu, 10 Apr 2025 03:45:27 +0000 + id: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.84 2025年03月号 + published_at: Mon, 12 May 2025 04:00:33 +0000 + id: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.82 2025年01月号 + published_at: Mon, 10 Mar 2025 04:00:33 +0000 + id: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.80 2024年12月号 + published_at: Mon, 10 Feb 2025 04:00:55 +0000 + id: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/ + title: "\U0001F3B2 ダイス×プログラミング『ニャイス!コード』を、CoderDojo に75台寄贈" + published_at: Fri, 14 Feb 2025 08:24:07 +0000 + id: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/ +- url: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/ + title: 米国系 IT 企業から CoderDojo へ、55 台のノート PC 寄贈 + published_at: Fri, 04 Apr 2025 10:00:32 +0000 + id: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/ From 8ad0b80dbf17790b393f3fb18589ece7f631161e Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 7 Jul 2025 09:54:39 +0900 Subject: [PATCH 09/31] =?UTF-8?q?refactor:=20=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E7=B5=B5=E6=96=87=E5=AD=97=E3=81=AE=E5=88=A4=E5=AE=9A?= =?UTF-8?q?=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/views/home/show.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb index deca2a61c..84cf0e5fe 100644 --- a/app/views/home/show.html.erb +++ b/app/views/home/show.html.erb @@ -179,7 +179,7 @@

    <% @news_items.each do |news| %>
  • - <% has_emoji = news.title.chars.any? { |char| char.match?(/[\p{Emoji}\p{Emoji_Component}&&[:^ascii:]]/) } %> + <% has_emoji = news.title.chars.any? { |char| char.match?(/[\p{Emoji}&&[^0-9#*]]/) } %> <% if has_emoji %> <% # 絵文字が含まれていれば、タイトルをそのまま表示 %> <%= link_to news.title, news.url, target: '_blank', rel: 'noopener noreferrer' %> From fd0a4d5421302e9be1ac2738a9582a4b76532d0e Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 9 Jul 2025 15:41:19 +0900 Subject: [PATCH 10/31] =?UTF-8?q?refactor:=20=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E8=A1=A8=E7=A4=BA=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 絵文字判定を全文字チェックから先頭文字のみに変更 - if文を三項演算子でシンプル化 - ロジックをヘルパーメソッド format_news_title に分離 --- app/helpers/application_helper.rb | 5 +++++ app/views/home/show.html.erb | 9 +-------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 83bad2c32..b965d923d 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -203,4 +203,9 @@ def translate_dojo_tag(tag_name) tag_translations[tag_name] || tag_name end + def format_news_title(news) + has_emoji = news.title[0]&.match?(/[\p{Emoji}&&[^0-9#*]]/) + has_emoji ? news.title : "📰 #{news.title}" + end + end diff --git a/app/views/home/show.html.erb b/app/views/home/show.html.erb index 84cf0e5fe..2e59646df 100644 --- a/app/views/home/show.html.erb +++ b/app/views/home/show.html.erb @@ -179,14 +179,7 @@
      <% @news_items.each do |news| %>
    • - <% has_emoji = news.title.chars.any? { |char| char.match?(/[\p{Emoji}&&[^0-9#*]]/) } %> - <% if has_emoji %> - <% # 絵文字が含まれていれば、タイトルをそのまま表示 %> - <%= link_to news.title, news.url, target: '_blank', rel: 'noopener noreferrer' %> - <% else %> - <% # 絵文字が含まれていなければ、「📰」をつけて表示 %> - 📰 <%= link_to news.title, news.url, target: '_blank', rel: 'noopener noreferrer' %> - <% end %> + <%= link_to format_news_title(news), news.url, target: '_blank', rel: 'noopener noreferrer' %>
    • <% end %>
    From 5003621ac53ed2462024e4617fe921716dc2a332 Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 10 Jul 2025 10:35:06 +0900 Subject: [PATCH 11/31] =?UTF-8?q?test:=20=E3=82=B5=E3=83=B3=E3=83=97?= =?UTF-8?q?=E3=83=AB=E3=83=8B=E3=83=A5=E3=83=BC=E3=82=B9=E3=81=AB=E7=B5=B5?= =?UTF-8?q?=E6=96=87=E5=AD=97=E3=81=82=E3=82=8A=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=B1=E3=83=BC=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 🎉 テスト記事③ を追加し、絵文字判定の両パターンをテスト可能に --- spec/fixtures/sample_news.rss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/fixtures/sample_news.rss b/spec/fixtures/sample_news.rss index 2e31f4c71..fcc9c5be5 100644 --- a/spec/fixtures/sample_news.rss +++ b/spec/fixtures/sample_news.rss @@ -19,5 +19,12 @@ サンプル記事の本文② + + 🎉 テスト記事③ + https://example.com/articles/3 + Wed, 03 Jun 2025 12:00:00 +0900 + 絵文字ありのサンプル記事の本文③ + + From c2b18185c4958287030bdba313e13284614e24da Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 14 Jul 2025 09:44:23 +0900 Subject: [PATCH 12/31] =?UTF-8?q?fix:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E6=94=B9=E8=A1=8C=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/helpers/application_helper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index b965d923d..da3d2f83f 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -207,5 +207,4 @@ def format_news_title(news) has_emoji = news.title[0]&.match?(/[\p{Emoji}&&[^0-9#*]]/) has_emoji ? news.title : "📰 #{news.title}" end - end From 18fa06cc63b4f895e188ec350f916e3b665c6eca Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 14 Jul 2025 09:50:34 +0900 Subject: [PATCH 13/31] =?UTF-8?q?fix:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=82=B9=E3=83=9A=E3=83=BC=E3=82=B9=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/fetch_news.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 481aeb5f5..729536d0f 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -15,7 +15,7 @@ namespace :news do # テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード feed_urls = if Rails.env.test? || Rails.env.staging? - [ Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s ] + [Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s] else [ 'https://news.coderdojo.jp/feed/', From 57c0d0ff3a4004a5cfaab440e8dc0bc7a1146067 Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 14 Jul 2025 11:53:14 +0900 Subject: [PATCH 14/31] =?UTF-8?q?refactor:=20=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E5=8F=96=E5=BE=97=E3=83=AD=E3=82=B8=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=92=E3=83=A2=E3=83=87=E3=83=AB=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/controllers/home_controller.rb | 2 +- app/models/news.rb | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index ab3cedc69..0604d289a 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -3,6 +3,6 @@ def show @dojo_count = Dojo.active_dojos_count @regions_and_dojos = Dojo.group_by_region_on_active @prefectures_and_dojos = Dojo.group_by_prefecture_on_active - @news_items = News.all.order(published_at: :desc).limit(7) + @news_items = News.recent.limit(7) end end diff --git a/app/models/news.rb b/app/models/news.rb index 193f62c49..cea5f50fc 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,2 +1,3 @@ class News < ApplicationRecord + scope :recent, -> { order(published_at: :desc) } end From ca825fd4afee2add52755913d3226ffa0c40be14 Mon Sep 17 00:00:00 2001 From: nacchan Date: Mon, 14 Jul 2025 14:26:02 +0900 Subject: [PATCH 15/31] =?UTF-8?q?=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=82=92=E3=83=96=E3=83=A9=E3=83=B3=E3=83=81?= =?UTF-8?q?=E3=81=A7=E3=82=82=E5=AE=9F=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/fetch_news.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/fetch_news.yml b/.github/workflows/fetch_news.yml index 9b54c3f3c..e6b4c5a7f 100644 --- a/.github/workflows/fetch_news.yml +++ b/.github/workflows/fetch_news.yml @@ -5,6 +5,9 @@ on: # 毎朝 9:00 JST - cron: '0 0 * * *' workflow_dispatch: + pull_request: + branches: + - "*" jobs: fetch: From 2cf6bcb27974bca3021eb72f4b9cc0d884750372 Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 15 Jul 2025 09:39:15 +0900 Subject: [PATCH 16/31] =?UTF-8?q?fix:=20ruby-version-file=20=E3=82=92=20ru?= =?UTF-8?q?by-version=20=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/fetch_news.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fetch_news.yml b/.github/workflows/fetch_news.yml index e6b4c5a7f..1399b6375 100644 --- a/.github/workflows/fetch_news.yml +++ b/.github/workflows/fetch_news.yml @@ -20,7 +20,7 @@ jobs: - name: Set up Ruby uses: ruby/setup-ruby@v1 with: - ruby-version-file: .ruby-version + ruby-version: .ruby-version bundler-cache: true - name: Install dependencies From d058245e0022af073e55ea199e631c79dc9b818c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 14 Jul 2025 05:33:27 +0000 Subject: [PATCH 17/31] chore: update news.yml via GitHub Actions --- db/news.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db/news.yml b/db/news.yml index 1b15bb587..1b2ae6162 100644 --- a/db/news.yml +++ b/db/news.yml @@ -12,10 +12,10 @@ news: title: DojoLetter Vol.85 2025年04月号 published_at: Tue, 10 Jun 2025 03:30:18 +0000 id: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2024/12/10/dojoletter-vol-78-2024%e5%b9%b410%e6%9c%88%e5%8f%b7/ - title: DojoLetter Vol.78 2024年10月号 - published_at: Tue, 10 Dec 2024 04:00:06 +0000 - id: https://news.coderdojo.jp/2024/12/10/dojoletter-vol-78-2024%e5%b9%b410%e6%9c%88%e5%8f%b7/ +- url: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.86 2025年05月号 + published_at: Thu, 10 Jul 2025 04:00:07 +0000 + id: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/ - url: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ title: DojoLetter Vol.83 2025年02月号 published_at: Thu, 10 Apr 2025 03:45:27 +0000 From 3a1bece15c649768e861500a812d6578a3c8e7a4 Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 15 Jul 2025 13:04:50 +0900 Subject: [PATCH 18/31] =?UTF-8?q?CoderDojo=20RSS=E3=83=8B=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=82=B9=E7=AE=A1=E7=90=86=E3=82=B7=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=A0=E3=81=AE=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit レビューに対応: - 全置換方式→追記・更新方式に変更 - 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構造を整理 --- lib/tasks/fetch_news.rake | 73 +++++++++++++++++++++++++++++++++------ 1 file changed, 63 insertions(+), 10 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 729536d0f..791c618a1 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -1,6 +1,7 @@ require 'rss' require 'open-uri' require 'yaml' +require 'time' require 'active_support/broadcast_logger' namespace :news do @@ -13,6 +14,14 @@ namespace :news do logger.info('==== START news:fetch ====') + # 既存の news.yml を読み込み + yaml_path = Rails.root.join('db', 'news.yml') + existing_news = if File.exist?(yaml_path) + YAML.load_file(yaml_path)['news'] || [] + else + [] + end + # テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード feed_urls = if Rails.env.test? || Rails.env.staging? [Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s] @@ -25,7 +34,7 @@ namespace :news do end # RSS 取得&パース - items = feed_urls.flat_map do |url| + new_items = feed_urls.flat_map do |url| logger.info("Fetching RSS → #{url}") begin URI.open(url) do |rss| @@ -44,19 +53,63 @@ namespace :news do end end - # 重複排除&日付降順ソート - unique = items.uniq { |i| i['url'] } - sorted = unique.sort_by { |i| i['published_at'] }.reverse + # 既存データをハッシュに変換(URL をキーに) + existing_items_hash = existing_news.index_by { |item| item['url'] } - # id を追加 - sorted.each { |i| i['id'] = i['url'] } + # 新しいアイテムと既存アイテムを分離 + truly_new_items = [] + updated_items = [] + + new_items.each do |new_item| + if existing_items_hash.key?(new_item['url']) + # 既存アイテムの更新 + existing_item = existing_items_hash[new_item['url']] + updated_item = existing_item.merge(new_item) # 新しい情報で更新 + updated_items << updated_item + else + # 完全に新しいアイテム + truly_new_items << new_item + end + end - # YAML に書き出し - File.open('db/news.yml', 'w') do |f| - f.write({ 'news' => sorted }.to_yaml) + # 既存の最大IDを取得 + max_existing_id = existing_news.map { |item| item['id'].to_i }.max || 0 + + # 新しいアイテムのみに ID を割り当て(古い順) + truly_new_items_sorted = truly_new_items.sort_by { |item| + Time.parse(item['published_at']) + } + + truly_new_items_sorted.each_with_index do |item, index| + item['id'] = max_existing_id + index + 1 end - logger.info("✅ Wrote #{sorted.size} items to db/news.yml") + # 更新されなかった既存アイテムを取得 + updated_urls = updated_items.map { |item| item['url'] } + unchanged_items = existing_news.reject { |item| updated_urls.include?(item['url']) } + + # 全アイテムをマージ + all_items = unchanged_items + updated_items + truly_new_items_sorted + + # 日付降順ソート + sorted_items = all_items.sort_by { |item| + Time.parse(item['published_at']) + }.reverse + + File.open('db/news.yml', 'w') do |f| + formatted_items = sorted_items.map do |item| + { + 'id' => item['id'], + 'url' => item['url'], + 'title' => item['title'], + 'published_at' => item['published_at'] + } + end + + f.write({ 'news' => formatted_items }.to_yaml) +end + + logger.info("✅ Wrote #{sorted_items.size} items to db/news.yml (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)") logger.info('==== END news:fetch ====') end end From edc41e68c754a88938bcd3c791663b65f0869cc4 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 16 Jul 2025 09:12:46 +0900 Subject: [PATCH 19/31] =?UTF-8?q?feat:=20News=20=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AB=E3=81=AB=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit バリデーション内容: ✅ title: 必須項目 ✅ url: 必須項目 + 重複禁止 + HTTP/HTTPS形式チェック ✅ published_at: 必須項目 目的: - データの整合性を保証 - 重複URLの防止 --- app/models/news.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/models/news.rb b/app/models/news.rb index cea5f50fc..9a303f12a 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,3 +1,9 @@ class News < ApplicationRecord scope :recent, -> { order(published_at: :desc) } + + validates :title, presence: true + validates :url, presence: true, + uniqueness: true, + format: { with: /\Ahttps?:\/\/.*\z/i } + validates :published_at, presence: true end From 21df7e22952594bfd1203b47cfd23e0740c8ccf6 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 16 Jul 2025 09:43:09 +0900 Subject: [PATCH 20/31] =?UTF-8?q?refactor:=20news.yml=20=E3=81=AE=E6=A7=8B?= =?UTF-8?q?=E9=80=A0=E3=82=92=E6=95=B4=E6=95=B0ID=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E3=81=AB=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - URLベースIDから整数IDに統一 - 連番管理で保守性向上 - 時系列順序での整理 --- db/news.yml | 64 ++++++++++++++++++++++++++--------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/db/news.yml b/db/news.yml index 1b2ae6162..c82230958 100644 --- a/db/news.yml +++ b/db/news.yml @@ -1,42 +1,42 @@ --- news: -- url: https://news.coderdojo.jp/2024/12/25/box-japan-to-coderdojo/ - title: "\U0001F384128台のノート PC 寄贈、Box Japan から CoderDojo へ \U0001F381" - published_at: Wed, 25 Dec 2024 11:00:40 +0000 - id: https://news.coderdojo.jp/2024/12/25/box-japan-to-coderdojo/ -- url: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/ - title: DojoLetter Vol.79 2024年11月号 - published_at: Tue, 14 Jan 2025 03:30:45 +0000 - id: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ - title: DojoLetter Vol.85 2025年04月号 - published_at: Tue, 10 Jun 2025 03:30:18 +0000 - id: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/ +- id: 10 + url: https://news.coderdojo.jp/2025/07/14/233-laptops-to-coderdojo/ + title: 米国系 IT 企業から CoderDojo へ、233 台のノート PC 寄贈 + published_at: Mon, 14 Jul 2025 05:50:31 +0000 +- id: 9 + url: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/ title: DojoLetter Vol.86 2025年05月号 published_at: Thu, 10 Jul 2025 04:00:07 +0000 - id: https://news.coderdojo.jp/2025/07/10/dojoletter-vol-86-2025%e5%b9%b405%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ - title: DojoLetter Vol.83 2025年02月号 - published_at: Thu, 10 Apr 2025 03:45:27 +0000 - id: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/ +- id: 8 + url: https://news.coderdojo.jp/2025/06/10/dojoletter-vol-85-2025%e5%b9%b404%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.85 2025年04月号 + published_at: Tue, 10 Jun 2025 03:30:18 +0000 +- id: 7 + url: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/ title: DojoLetter Vol.84 2025年03月号 published_at: Mon, 12 May 2025 04:00:33 +0000 - id: https://news.coderdojo.jp/2025/05/12/dojoletter-vol-84-2025%e5%b9%b403%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/ +- id: 6 + url: https://news.coderdojo.jp/2025/04/10/dojoletter-vol-83-2025%e5%b9%b402%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.83 2025年02月号 + published_at: Thu, 10 Apr 2025 03:45:27 +0000 +- id: 5 + url: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/ + title: 米国系 IT 企業から CoderDojo へ、55 台のノート PC 寄贈 + published_at: Fri, 04 Apr 2025 10:00:32 +0000 +- id: 4 + url: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/ title: DojoLetter Vol.82 2025年01月号 published_at: Mon, 10 Mar 2025 04:00:33 +0000 - id: https://news.coderdojo.jp/2025/03/10/dojoletter-vol-82-2025%e5%b9%b401%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/ - title: DojoLetter Vol.80 2024年12月号 - published_at: Mon, 10 Feb 2025 04:00:55 +0000 - id: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/ -- url: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/ +- id: 3 + url: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/ title: "\U0001F3B2 ダイス×プログラミング『ニャイス!コード』を、CoderDojo に75台寄贈" published_at: Fri, 14 Feb 2025 08:24:07 +0000 - id: https://news.coderdojo.jp/2025/02/14/coderdojo-de-nyaicecode/ -- url: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/ - title: 米国系 IT 企業から CoderDojo へ、55 台のノート PC 寄贈 - published_at: Fri, 04 Apr 2025 10:00:32 +0000 - id: https://news.coderdojo.jp/2025/04/04/55-laptops-to-coderdojo/ +- id: 2 + url: https://news.coderdojo.jp/2025/02/10/dojoletter-vol-80-2024%e5%b9%b412%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.80 2024年12月号 + published_at: Mon, 10 Feb 2025 04:00:55 +0000 +- id: 1 + url: https://news.coderdojo.jp/2025/01/14/dojoletter-vol-79-2024%e5%b9%b411%e6%9c%88%e5%8f%b7/ + title: DojoLetter Vol.79 2024年11月号 + published_at: Tue, 14 Jan 2025 03:30:45 +0000 From 776767cf00080161c5e2c78437397a19344abfb1 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 16 Jul 2025 12:50:39 +0900 Subject: [PATCH 21/31] =?UTF-8?q?feat:=20=E7=B5=B5=E6=96=87=E5=AD=97?= =?UTF-8?q?=E3=83=98=E3=83=AB=E3=83=91=E3=83=BC=E3=81=AE=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - format_news_title メソッドのテストを実装 - 先頭文字が絵文字なら保持、そうでなければ📰を付与 --- spec/helpers/application_helper_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 spec/helpers/application_helper_spec.rb diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb new file mode 100644 index 000000000..182d901b2 --- /dev/null +++ b/spec/helpers/application_helper_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +RSpec.describe ApplicationHelper, type: :helper do + describe '#format_news_title' do + it '先頭文字が絵文字ならそのまま、そうでなければ 📰 を付与する' do + { + '🔔 新着' => '🔔 新着', + '更新情報' => '📰 更新情報', + '1つ目のお知らせ' => '📰 1つ目のお知らせ' + }.each do |input, expected| + news = double('news', title: input) + expect(helper.format_news_title(news)).to eq expected + end + end + end +end From 848eda19caee6dbb986a9b6d88906c07e954c409 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 16 Jul 2025 13:28:24 +0900 Subject: [PATCH 22/31] =?UTF-8?q?refactor:=20News=20=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=AA=E3=83=BC=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 無効なURLを有効なURL形式に修正 - sequence でユニークなタイトル・URL生成 - バリデーション要件を満たすように改善 - 動的な published_at に変更 --- spec/factories/news.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/factories/news.rb b/spec/factories/news.rb index 4bfe4e543..26b683ba7 100644 --- a/spec/factories/news.rb +++ b/spec/factories/news.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :news do - title { "MyString" } - url { "MyString" } - published_at { "2025-06-30 13:06:11" } + sequence(:title) { |n| "Test News Article #{n}" } + sequence(:url) { |n| "https://news.coderdojo.jp/#{n}" } + published_at { 1.day.ago } end end From e29707819a391cd04bb651716e6985a906c04869 Mon Sep 17 00:00:00 2001 From: nacchan Date: Wed, 16 Jul 2025 13:30:52 +0900 Subject: [PATCH 23/31] =?UTF-8?q?refactor:=20=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=BB=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 特定記事に依存しないテストに変更 - /#news での表示機能をテスト - ファクトリーを使用してメンテナンス性向上 --- spec/features/news_spec.rb | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/spec/features/news_spec.rb b/spec/features/news_spec.rb index dde44640d..5f3155686 100644 --- a/spec/features/news_spec.rb +++ b/spec/features/news_spec.rb @@ -1,11 +1,16 @@ # -*- coding: utf-8 -*- require 'rails_helper' -RSpec.feature "News", type: :feature do - describe "GET /news/2016/12/12/new-backend" do - scenario "Title should be formatted" do - visit "/docs/post-backend-update-history" - expect(page).to have_title "CoderDojo Japan のバックエンド刷新" +RSpec.feature "NewsSection", type: :feature do + let!(:news_item) { create(:news) } + + scenario "ニュースセクションにニュース項目が表示される" do + visit root_path(anchor: 'news') + + within 'section#news' do + expect(page).to have_link(href: news_item.url) + expect(page).to have_content(news_item.title) + expect(page).to have_selector("a[target='_blank']") end end end From da8638a7ce36271f74ab9cfda0672fc0888e4a94 Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 17 Jul 2025 10:58:08 +0900 Subject: [PATCH 24/31] =?UTF-8?q?fix:=20RSS=E5=8F=96=E5=BE=97=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AE=E3=82=BB=E3=82=AD=E3=83=A5=E3=83=AA=E3=83=86?= =?UTF-8?q?=E3=82=A3=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SSRF対策として Net::HTTP + URL検証を導入 - open-uri の利用を廃止し safe_open メソッドを追加 --- lib/tasks/fetch_news.rake | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 791c618a1..faf2375ce 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -1,9 +1,23 @@ require 'rss' -require 'open-uri' +require 'net/http' +require 'uri' require 'yaml' require 'time' require 'active_support/broadcast_logger' +def safe_open(url) + uri = URI.parse(url) + unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) + raise "不正なURLです: #{url}" + end + + Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| + request = Net::HTTP::Get.new(uri) + response = http.request(request) + response.body + end +end + namespace :news do desc 'RSS フィードから最新ニュースを取得し、db/news.yml に書き出す' task fetch: :environment do @@ -37,15 +51,14 @@ namespace :news do new_items = feed_urls.flat_map do |url| logger.info("Fetching RSS → #{url}") begin - URI.open(url) do |rss| - feed = RSS::Parser.parse(rss, false) - feed.items.map do |item| - { - 'url' => item.link, - 'title' => item.title, - 'published_at' => item.pubDate.to_s - } - end + rss = safe_open(url) + feed = RSS::Parser.parse(rss, false) + feed.items.map do |item| + { + 'url' => item.link, + 'title' => item.title, + 'published_at' => item.pubDate.to_s + } end rescue => e logger.warn("⚠️ Failed to fetch #{url}: #{e.message}") From 81e7ebc85ac51225e8bcb9b3ffa13f3f74b283e1 Mon Sep 17 00:00:00 2001 From: nacchan Date: Thu, 17 Jul 2025 11:52:11 +0900 Subject: [PATCH 25/31] =?UTF-8?q?fix:=20YAML=E3=81=AE=E5=AE=89=E5=85=A8?= =?UTF-8?q?=E3=81=AA=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - YAML.load_file から YAML.safe_load へ置き換え --- lib/tasks/fetch_news.rake | 2 +- lib/tasks/import_news.rake | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index faf2375ce..932c3f73f 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -31,7 +31,7 @@ namespace :news do # 既存の news.yml を読み込み yaml_path = Rails.root.join('db', 'news.yml') existing_news = if File.exist?(yaml_path) - YAML.load_file(yaml_path)['news'] || [] + YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)['news'] || [] else [] end diff --git a/lib/tasks/import_news.rake b/lib/tasks/import_news.rake index ed4f589a7..3a0351e40 100644 --- a/lib/tasks/import_news.rake +++ b/lib/tasks/import_news.rake @@ -4,7 +4,7 @@ namespace :news do desc "db/news.yml を読み込んで News テーブルを upsert する" task import_from_yaml: :environment do yaml_path = Rails.root.join('db', 'news.yml') - raw = YAML.load_file(yaml_path) + raw = YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true) # entries を計算 entries = raw['news'] || [] From fc6f171ba683f3d97ae94555da53c3649e5a5c0b Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 08:56:35 +0900 Subject: [PATCH 26/31] =?UTF-8?q?chore:=20pull=5Frequest=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=82=AC=E3=83=BC=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/fetch_news.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/fetch_news.yml b/.github/workflows/fetch_news.yml index 1399b6375..7df780d13 100644 --- a/.github/workflows/fetch_news.yml +++ b/.github/workflows/fetch_news.yml @@ -5,9 +5,6 @@ on: # 毎朝 9:00 JST - cron: '0 0 * * *' workflow_dispatch: - pull_request: - branches: - - "*" jobs: fetch: From 7733a59b4874908b1c7000fe6880964db9960aaa Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 09:17:58 +0900 Subject: [PATCH 27/31] =?UTF-8?q?style:=20News=E3=83=A2=E3=83=87=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88=E3=81=A8?= =?UTF-8?q?=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E8=A8=98=E8=BF=B0=E3=82=92=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - インデントを2スペースに統一 - validatesの値部分を縦に揃えて可読性を向上 --- app/models/news.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/models/news.rb b/app/models/news.rb index 9a303f12a..0dd08cbc2 100644 --- a/app/models/news.rb +++ b/app/models/news.rb @@ -1,9 +1,9 @@ class News < ApplicationRecord - scope :recent, -> { order(published_at: :desc) } + scope :recent, -> { order(published_at: :desc) } - validates :title, presence: true - validates :url, presence: true, - uniqueness: true, - format: { with: /\Ahttps?:\/\/.*\z/i } - validates :published_at, presence: true + validates :title, presence: true + validates :url, presence: true, + uniqueness: true, + format: { with: /\Ahttps?:\/\/.*\z/i } + validates :published_at, presence: true end From ac923e860a9f9f2e9562bfdf03c16d6e975f78f4 Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 09:29:00 +0900 Subject: [PATCH 28/31] =?UTF-8?q?style:=20if=E6=96=87=E3=81=AE=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88=E8=AA=BF=E6=95=B4=E3=81=A8?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E3=82=AB=E3=83=B3=E3=83=9E=E3=81=AE?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - if/else/endの位置を揃えて可読性を向上 - feed_urls配列の末尾カンマを削除し、コメント位置も整理 --- lib/tasks/fetch_news.rake | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 932c3f73f..14cdb7fb9 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -31,21 +31,21 @@ namespace :news do # 既存の news.yml を読み込み yaml_path = Rails.root.join('db', 'news.yml') existing_news = if File.exist?(yaml_path) - YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)['news'] || [] - else - [] - end + YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)['news'] || [] + else + [] + end # テスト/ステージング環境ではサンプルファイル、本番は実サイトのフィード feed_urls = if Rails.env.test? || Rails.env.staging? - [Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s] - else - [ - 'https://news.coderdojo.jp/feed/', - # 必要に応じて他 Dojo の RSS もここに追加可能 - # 'https://coderdojotokyo.org/feed', - ] - end + [Rails.root.join('spec', 'fixtures', 'sample_news.rss').to_s] + else + [ + 'https://news.coderdojo.jp/feed/' + # 必要に応じて他 Dojo の RSS もここに追加可能 + # 'https://coderdojotokyo.org/feed', + ] + end # RSS 取得&パース new_items = feed_urls.flat_map do |url| From 5f1b8bdf8b1ab20027b36321a1b93fb5cd1a5f50 Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 09:49:09 +0900 Subject: [PATCH 29/31] =?UTF-8?q?style:=20safe=5Fopen=E3=81=AEURL=E6=A4=9C?= =?UTF-8?q?=E8=A8=BC=E3=82=92=E5=BE=8C=E7=BD=AEunless=E3=81=A7=E7=B0=A1?= =?UTF-8?q?=E6=BD=94=E3=81=AB=E8=A8=98=E8=BF=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/fetch_news.rake | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 14cdb7fb9..177e9d198 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -7,9 +7,7 @@ require 'active_support/broadcast_logger' def safe_open(url) uri = URI.parse(url) - unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) - raise "不正なURLです: #{url}" - end + raise "不正なURLです: #{url}" unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS) Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| request = Net::HTTP::Get.new(uri) From cddb4abcd66aaf325ae1fbc3d62a94698af9b74a Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 10:18:38 +0900 Subject: [PATCH 30/31] =?UTF-8?q?style:=20=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=81=A8YAML=E6=9B=B8=E3=81=8D=E5=87=BA=E3=81=97?= =?UTF-8?q?=E3=81=AE=E3=82=A4=E3=83=B3=E3=83=87=E3=83=B3=E3=83=88=E6=95=B4?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 「既存データをハッシュに変換」コメントのインデントを修正 - File.open内のformatted_items生成部分のインデントを整理 --- lib/tasks/fetch_news.rake | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/tasks/fetch_news.rake b/lib/tasks/fetch_news.rake index 177e9d198..415d5df25 100644 --- a/lib/tasks/fetch_news.rake +++ b/lib/tasks/fetch_news.rake @@ -64,7 +64,7 @@ namespace :news do end end - # 既存データをハッシュに変換(URL をキーに) + # 既存データをハッシュに変換(URL をキーに) existing_items_hash = existing_news.index_by { |item| item['url'] } # 新しいアイテムと既存アイテムを分離 @@ -108,17 +108,17 @@ namespace :news do }.reverse File.open('db/news.yml', 'w') do |f| - formatted_items = sorted_items.map do |item| - { - 'id' => item['id'], - 'url' => item['url'], - 'title' => item['title'], - 'published_at' => item['published_at'] - } - end - - f.write({ 'news' => formatted_items }.to_yaml) -end + formatted_items = sorted_items.map do |item| + { + 'id' => item['id'], + 'url' => item['url'], + 'title' => item['title'], + 'published_at' => item['published_at'] + } + end + + f.write({ 'news' => formatted_items }.to_yaml) + end logger.info("✅ Wrote #{sorted_items.size} items to db/news.yml (#{truly_new_items_sorted.size} new, #{updated_items.size} updated)") logger.info('==== END news:fetch ====') From 0694996e9dab0eb994b95828eb5d1f2d0ec80933 Mon Sep 17 00:00:00 2001 From: nacchan Date: Tue, 22 Jul 2025 10:25:40 +0900 Subject: [PATCH 31/31] =?UTF-8?q?style:=20=E3=83=80=E3=83=96=E3=83=AB?= =?UTF-8?q?=E3=82=AF=E3=82=A9=E3=83=BC=E3=83=88=E3=82=92=E3=82=B7=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=83=AB=E3=82=AF=E3=82=A9=E3=83=BC=E3=83=88=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/tasks/import_news.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/import_news.rake b/lib/tasks/import_news.rake index 3a0351e40..bebe76611 100644 --- a/lib/tasks/import_news.rake +++ b/lib/tasks/import_news.rake @@ -1,7 +1,7 @@ require 'yaml' namespace :news do - desc "db/news.yml を読み込んで News テーブルを upsert する" + desc 'db/news.yml を読み込んで News テーブルを upsert する' task import_from_yaml: :environment do yaml_path = Rails.root.join('db', 'news.yml') raw = YAML.safe_load(File.read(yaml_path), permitted_classes: [Time], aliases: true)