Bulk Ownership Change of First Posts by Tag – Script Review and Suggestions

AI-generated summary

Ethsim2 seeks feedback on a Ruby script to bulk change the ownership of first posts in topics tagged “ethan” from the system user to “Ethan_Hoom1” on a self-hosted Discourse instance. The script uses PostOwnerChanger to update ownership while avoiding revisions, with options for dry-run and limiting the number of posts processed. The user asks about potential edge cases with PostOwnerChanger, whether to also update the topic.user field, and recommendations for safety and performance. The script includes robust error handling and logging, and leverages Discourse’s official methods for ownership changes.

Hello,

I am looking to change the ownership of all opening posts in event topics that are tagged with “ethan” such that the owner changes from “system” to the user “Ethan_Hoom1”. I am running a self-hosted instance and have Rails console access.

After reviewing the recommendations at Administrative Bulk Operations and Change ownership of all posts by a specific user, I have prepared the following script. I would appreciate any suggestions or best practices before running it in production:


# Find the tag object with the name "ethan"
tag = Tag.find_by(name: "ethan")

# Stop immediately if the tag does not exist
raise "Tag not found" unless tag


# Find the user who should become the new owner of the posts
# username_lower is safer than username (case-insensitive)
new_owner = User.find_by(username_lower: "ethan_hoom1")

# Stop immediately if the target user does not exist
raise "New owner user not found" unless new_owner


# Find the system user account
# Fallback to Discourse.system_user in case the record lookup fails
system_user = User.find_by(username_lower: "system") || Discourse.system_user

# Stop immediately if the system user cannot be found
raise "System user not found" unless system_user


# When true, the script will only print what it WOULD change
# and will not modify anything in the database
DRY_RUN = true

# Optional safety limit for first run (e.g. 10 or 50)
# Set to nil to process all matching posts
LIMIT = nil


# Build an ActiveRecord query for the posts we want to modify
scope = Post
  # Join posts → topics → topic_tags so we can filter by tag
  .joins(topic: :topic_tags)

  # Only target the opening post in each topic
  .where(post_number: 1)

  # Only target posts currently owned by the system user
  .where(user_id: system_user.id)

  # Only include topics that have the "ethan" tag
  .where(topic_tags: { tag_id: tag.id })

  # Exclude private messages and deleted topics
  .where(topics: {
    archetype: Archetype.default,
    deleted_at: nil
  })


# If LIMIT is set, restrict how many posts are processed
scope = scope.limit(LIMIT) if LIMIT


# Iterate through matching posts in batches to avoid memory issues
scope.find_each(batch_size: 100) do |first_post|

  # Store the topic ID for logging output
  topic_id = first_post.topic_id


  # If dry-run mode is enabled, do not modify anything
  if DRY_RUN
    puts "[DRY RUN] Would change owner for topic #{topic_id} (post #{first_post.id})"
    next
  end


  begin
    # Use Discourse's official ownership-changing service
    PostOwnerChanger.new(
      # Only change ownership of the opening post
      post_ids: [first_post.id],

      # The topic containing the post
      topic_id: topic_id,

      # The user who should become the new owner
      new_owner: new_owner,

      # The user performing the action (system user)
      acting_user: system_user,

      # Do not create an edit revision for this change
      skip_revision: true
    ).change_owner!


    # Log success to the console
    puts "Changed owner for topic #{topic_id} (post #{first_post.id})"

  rescue => e
    # If anything fails, log the error but continue processing others
    puts "FAILED topic #{topic_id} (post #{first_post.id}): #{e.class}: #{e.message}"
  end
end

Questions:

  • Are there any caveats or edge cases I should be aware of with PostOwnerChanger for this use case?
  • Is it advisable to also update the topic.user field as shown in the optional line?
  • Do you have any recommendations for further safety or performance improvements regarding this batch process?

Thank you for your help and for all the great documentation!

1 Like