タグによる最初の投稿の一括所有者変更 – スクリプトのレビューと提案

こんにちは。

イベントトピックの「ethan」タグが付いたすべての最初の投稿の所有権を「system」からユーザー「Ethan_Hoom1」に変更したいと考えています。セルフホストインスタンスで運用しており、Railsコンソールへのアクセス権があります。

Administrative Bulk Operations および Change ownership of all posts by a specific user の推奨事項を確認した後、以下のスクリプトを作成しました。本番環境で実行する前に、ご意見やベストプラクティスがあれば教えていただけますでしょうか。


# 「ethan」という名前のタグオブジェクトを検索
tag = Tag.find_by(name: "ethan")

# タグが見つからない場合は直ちに停止
raise "Tag not found" unless tag


# 投稿の新しい所有者となるユーザーを検索
# username_lower の方が username (大文字・小文字を区別しない) よりも安全
new_owner = User.find_by(username_lower: "ethan_hoom1")

# 対象ユーザーが見つからない場合は直ちに停止
raise "New owner user not found" unless new_owner


# system ユーザーアカウントを検索
# レコード検索が失敗した場合に備えて Discourse.system_user にフォールバック
system_user = User.find_by(username_lower: "system") || Discourse.system_user

# system ユーザーが見つからない場合は直ちに停止
raise "System user not found" unless system_user


# true の場合、スクリプトは変更内容のみを出力し、データベースへの変更は行いません
DRY_RUN = true

# 最初の実行のためのオプションの安全制限(例:10 または 50)
# nil に設定すると、一致するすべての投稿を処理します
LIMIT = nil


# 変更したい投稿の ActiveRecord クエリを構築
scope = Post
  # 投稿 → トピック → topic_tags を結合してタグでフィルタリングできるようにする
  .joins(topic: :topic_tags)

  # 各トピックの最初の投稿のみを対象とする
  .where(post_number: 1)

  # 現在 system ユーザーが所有している投稿のみを対象とする
  .where(user_id: system_user.id)

  # 「ethan」タグを持つトピックのみを含める
  .where(topic_tags: { tag_id: tag.id })

  # プライベートメッセージや削除されたトピックを除外する
  .where(topics: {
    archetype: Archetype.default,
    deleted_at: nil
  })


# LIMIT が設定されている場合、処理する投稿数を制限する
scope = scope.limit(LIMIT) if LIMIT


# メモリ問題を避けるため、一致する投稿をバッチで反復処理する
scope.find_each(batch_size: 100) do |first_post|

  # ログ出力のためにトピックIDを保存
  topic_id = first_post.topic_id


  # ドライランモードが有効な場合、何も変更しない
  if DRY_RUN
    puts "[DRY RUN] Would change owner for topic #{topic_id} (post #{first_post.id})"
    next
  end


  begin
    # Discourse の公式の所有権変更サービスを使用する
    PostOwnerChanger.new(
      # 最初の投稿の所有権のみを変更
      post_ids: [first_post.id],

      # 投稿を含むトピック
      topic_id: topic_id,

      # 新しい所有者となるユーザー
      new_owner: new_owner,

      # アクションを実行するユーザー(system ユーザー)
      acting_user: system_user,

      # この変更に対するリビジョン作成をスキップする
      skip_revision: true
    ).change_owner!


    # 成功をコンソールにログ出力する
    puts "Changed owner for topic #{topic_id} (post #{first_post.id})"

  rescue => e
    # 何か失敗した場合、エラーをログ出力するが、他の処理は続行する
    puts "FAILED topic #{topic_id} (post #{first_post.id}): #{e.class}: #{e.message}"
  end
end

質問:

  • このユースケースにおける PostOwnerChanger に関して、注意すべき点やエッジケースはありますか?
  • オプションの行で示されているように、topic.user フィールドも更新する方が賢明でしょうか?
  • このバッチ処理に関して、さらなる安全性やパフォーマンス向上のための推奨事項はありますか?

ご協力と素晴らしいドキュメントに感謝いたします!

「いいね!」 1