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.userfield 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!