用 Claude3.7 を使って SQL クエリを Rake タスクに変換し、直接 JSON にエクスポートする方法です。
lib/tasks/custom.rake
# frozen_string_literal: true
desc 'すべてのトピックと投稿に external_id を追加'
task 'custom:add-external-id', [:override_existing] => :environment do |task, args|
# `rake 'custom:add-external-id[1]'` を使用して既存のトピックと投稿の external_id を上書き
# トピック内の最初の投稿がトピックと同じ external_id を持つようにする
require 'parallel'
require 'securerandom'
Parallel.each(Post.all, progress: "Posts") do |post|
if args[:override_existing].present? || post.external_id.blank?
post.update_column(:external_id, SecureRandom.alphanumeric(SiteSetting.external_id_length))
if post.post_number == 1
topic = Topic.find(post.topic_id)
topic.update_column(:external_id, post.external_id)
end
end
end
end
# rake custom:export-users > users.json
# rake custom:export-users > /home/discourse/public-export/users.json
desc "ユーザー情報(センサティブデータ除く)をエクスポート"
task "custom:export-users" => :environment do
require 'json'
a = []
User.find_each(batch_size: 100_000) do |user|
payload = {
id: user.id,
username: user.username,
name: user.name,
admin: user.admin,
moderator: user.moderator,
trust_level: user.trust_level,
avatar_template: user.avatar_template,
title: user.title,
groups: user.groups.map { |i| i.name },
locale: user.locale,
silenced_till: user.silenced_till,
staged: user.staged,
active: user.active,
created_at: user.created_at.to_i,
updated_at: user.updated_at.to_i
}
a.push payload
end
puts a.to_json
end
# rake "custom:export-posts[0,/home/discourse/public-export/posts.json]"
desc "非制限カテゴリの投稿データをエクスポート"
task "custom:export-posts", [:min_id, :output_file] => :environment do |_, args|
require 'json'
min_id = (args[:min_id] || 0).to_i
output_file = args[:output_file]
puts "ID #{min_id} より大きい投稿をエクスポート中..."
base_scope = Post.joins(:topic)
.joins("JOIN categories c ON c.id = topics.category_id")
.where("NOT c.read_restricted")
.where("topics.deleted_at IS NULL")
.where("posts.deleted_at IS NULL")
.where(post_type: 1)
.where(hidden: false)
.where("posts.id > ?", min_id)
# 選択フィールドを除いたカウント
total = base_scope.count
puts "#{total} 件の投稿を見つけました"
posts_data = []
# 実際のデータ取得に必要な select と order を追加
scope = base_scope.select("posts.id, posts.raw, posts.cooked, posts.post_number,
posts.topic_id, posts.user_id, posts.created_at,
posts.updated_at, posts.reply_to_post_number,
posts.reply_to_user_id, posts.reply_count,
topics.like_count, topics.word_count")
.order("posts.id ASC")
progress = 0
scope.find_each(batch_size: 1000) do |post|
posts_data << {
id: post.id,
raw: post.raw,
cooked: post.cooked,
post_number: post.post_number,
topic_id: post.topic_id,
user_id: post.user_id,
created_at: post.created_at,
updated_at: post.updated_at,
reply_to_post_number: post.reply_to_post_number,
reply_to_user_id: post.reply_to_user_id,
reply_count: post.reply_count,
like_count: post.topic.like_count,
word_count: post.topic.word_count
}
progress += 1
if progress % 1000 == 0
puts "プログレス: #{progress}/#{total} 投稿処理完了"
end
end
result = posts_data.to_json
if output_file
File.write(output_file, result)
puts "ファイル #{output_file} にエクスポート完了"
else
puts result
end
puts "エクスポート完了。総投稿数: #{posts_data.size}"
end
# rake "custom:export-topics[0,/home/discourse/public-export/topics.json]"
desc "非制限カテゴリのトピックデータをエクスポート"
task "custom:export-topics", [:min_id, :output_file] => :environment do |_, args|
require 'json'
min_id = (args[:min_id] || 0).to_i
output_file = args[:output_file]
puts "ID #{min_id} より大きいトピックをエクスポート中..."
# クエリのベーススコープを作成
base_scope = Topic.joins(:category)
.where("NOT categories.read_restricted")
.where("topics.deleted_at IS NULL")
.where(archetype: 'regular')
.where("topics.id > ?", min_id)
# 選択フィールドを含まないレコード数カウント
total = base_scope.count
puts "#{total} 件のトピックを見つけました"
# エクスポート対象がない場合は早期終了
if total == 0
puts "エクスポートするトピックはありません。"
return
end
topics_data = []
progress = 0
# メモリの問題を避けるためバッチ処理
base_scope.includes(:tags)
.order(id: :asc)
.find_each(batch_size: 1000) do |topic|
topics_data << {
id: topic.id,
category_name: topic.category.name,
category_id: topic.category_id,
title: topic.title,
excerpt: topic.excerpt,
created_at: topic.created_at,
last_posted_at: topic.last_posted_at,
updated_at: topic.updated_at,
views: topic.views,
posts_count: topic.posts_count,
like_count: topic.like_count,
user_id: topic.user_id,
last_post_user_id: topic.last_post_user_id,
tags: topic.tags.map { |i| i.name }
}
progress += 1
if progress % 1000 == 0
puts "プログレス: #{progress}/#{total} トピック処理完了"
end
end
result = topics_data.to_json
if output_file
File.write(output_file, result)
puts "ファイル #{output_file} にエクスポート完了"
else
puts result
end
puts "エクスポート完了。総トピック数: #{topics_data.size}"
end
# rake "custom:export-likes[0,/home/discourse/public-export/likes.json]"
desc "非制限カテゴリの投稿に対する「いいね」データをエクスポート"
task "custom:export-likes", [:min_id, :output_file] => :environment do |_, args|
require 'json'
min_id = (args[:min_id] || 0).to_i
output_file = args[:output_file]
puts "ID #{min_id} より大きい「いいね」データをエクスポート中..."
# 対象となる投稿の ID を取得
qualifying_posts = Post.joins(:topic)
.joins("JOIN categories c ON c.id = topics.category_id")
.where("NOT c.read_restricted")
.where("topics.deleted_at IS NULL")
.where("posts.deleted_at IS NULL")
.where(post_type: 1)
.where(hidden: false)
.pluck(:id)
puts "#{qualifying_posts.size} 件の対象投稿を見つけました"
# 対象の「いいね」データを取得
base_scope = PostAction
.where(post_action_type_id: 2) # 2 は「いいね」アクションタイプ
.where(deleted_at: nil)
.where("id > ?", min_id)
.where(post_id: qualifying_posts)
# 対象となる「いいね」データの総数をカウント
total = base_scope.count
puts "#{total} 件の「いいね」データを見つけました"
# エクスポート対象がない場合は早期終了
if total == 0
puts "エクスポートする「いいね」データはありません。"
return
end
# データを取得して並べ替え
likes_scope = base_scope
.select(:id, :post_id, :user_id, :created_at)
.order(id: :asc)
likes_data = []
progress = 0
# メモリの問題を避けるためバッチ処理
likes_scope.find_each(batch_size: 1000) do |like|
likes_data << {
post_id: like.post_id,
user_id: like.user_id,
created_at: like.created_at
}
progress += 1
if progress % 1000 == 0
puts "プログレス: #{progress}/#{total} 「いいね」処理完了"
end
end
result = likes_data.to_json
if output_file
File.write(output_file, result)
puts "ファイル #{output_file} にエクスポート完了"
else
puts result
end
puts "エクスポート完了。総「いいね」数: #{likes_data.size}"
end
``````ruby
# rake "custom:export-all[0,/home/discourse/public-export]"
desc "非制限カテゴリから全データ(トピック、投稿、いいね、ユーザー)をエクスポート"
task "custom:export-all", [:min_id, :output_dir] => :environment do |_, args|
min_id = args[:min_id] || 0
output_dir = args[:output_dir] || "/home/discourse/public-export"
# 出力ディレクトリの存在確認
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
# 出力ファイルパスの定義
topics_file = File.join(output_dir, "topics.json")
posts_file = File.join(output_dir, "posts.json")
likes_file = File.join(output_dir, "likes.json")
users_file = File.join(output_dir, "users.json")
puts "全データを #{output_dir} にエクスポートを開始します..."
# トピックのエクスポート
puts "\n=== トピックのエクスポート ==="
Rake::Task["custom:export-topics"].invoke(min_id, topics_file)
Rake::Task["custom:export-topics"].reenable
# 投稿のエクスポート
puts "\n=== 投稿のエクスポート ==="
Rake::Task["custom:export-posts"].invoke(min_id, posts_file)
Rake::Task["custom:export-posts"].reenable
# いいねのエクスポート
puts "\n=== いいねのエクスポート ==="
Rake::Task["custom:export-likes"].invoke(min_id, likes_file)
Rake::Task["custom:export-likes"].reenable
# ユーザーのエクスポート
puts "\n=== ユーザーのエクスポート ==="
# 出力をファイルにリダイレクト(ユーザータスクは標準出力に出力するため)
original_stdout = $stdout
File.open(users_file, 'w') do |f|
$stdout = f
Rake::Task["custom:export-users"].invoke
Rake::Task["custom:export-users"].reenable
end
$stdout = original_stdout
puts "\n=== エクスポート完了 ==="
puts "トピックは #{topics_file} にエクスポートされました"
puts "投稿は #{posts_file} にエクスポートされました"
puts "いいねは #{likes_file} にエクスポートされました"
puts "ユーザーは #{users_file} にエクスポートされました"
end