שגיאת off-by-one מוזרה לאחר עדכון

סיכום מבוסס בינה מלאכותית

pfaffman is troubleshooting a Rails script that updates posts and encountering a mysterious off-by-one error — where a post gets updated with the raw content from a previous post, particularly affecting the topic post of topic N with data from the final post of topic N-1. After failed attempts to fix it using gsub! directly on post.raw, they switched to using post.revise with explicit user and options to create proper revisions. The script targets posts auto-generated from Slack, matching a regex to reformat content — moving Slack thread links and stripping user names from replies. The script processes topic posts first, then iterates through subsequent posts in each topic, revising them only if the cleaned content exceeds 10 characters. Despite not fully diagnosing the root cause of the off-by-one issue, this revised version appears to work correctly by avoiding in-place mutations and using Discourse’s revision system. pfaffman notes the confusion around why the topic post was being overwritten with the previous topic’s final post’s content, suggesting a possible state or scope issue in the loop.

I’m trying to update a set of posts in a script in rails. It seems that there is some strange off-by-one behavior where one post gets updated with the previous post’s value.

I moved all of these updates to directly do a post.raw.gsub() to “solve” this problem for the posts within the inner loop (which had previously been getting the value from the topic post), but now the topic post of topic N gets updated with the raw of the previous final post from topic N-1.

I’m extra double confused now, as the first (i.e. topic) post gets updated with tpost.raw.gsub! with the value of post.raw.gsub!(preg,"#{$2}") from the previous interation.

EDIT: Well, I never did figure out what was going wrong with the last version, but here’s a version of an update-some-posts script that creates a revision:

def fix_slack_posts
  opts = { bypass_rate_limiter: true, bypass_bump: true, skip_validations: true }
  reg=/(\*\*)(This topic was automatically generated from Slack. You can find the original thread \[here\].+?\))(\*\*\.)?\s*?([a-zA-Z, ()]* : )(.*)/m
  preg = /([a-zA-Z, ()]+? : )(.*)/m
  topic_posts = Post.where("raw like '**This topic was automatically%'")
  #topic_posts = Post.where(topic_id: [23934, 23935], post_number: 1)
  topic_posts.each do |tpost|
    begin
      puts "Fixing topic post #{tpost.id} with #{tpost.raw[0..30]}"
      puts tpost.raw.gsub(reg,"1 #{$1}\n2 #{$2}\n3 #{$3}\n4 #{$4}\n 5 #{$5}")
      raw = tpost.raw.gsub(reg,"#{$5}\n\n#{$2}.")
      puts "Fixing topic post #{tpost.id} with #{tpost.raw[0..30]}\n--->#{raw}"
      user = User.find(tpost.user_id)
      if raw.length > 10
        tpost.revise(user,{raw: raw,edit_reason: 'fix slack link location'}, opts)
      end
    rescue
      puts "Can't update topic post #{tpost.raw}"
    end
    posts = Post.where(topic_id: tpost.topic_id).where("post_number > 1")
    posts.each do |post|
      if post.raw.gsub(preg,"#{$2}").length>=10
        begin
          puts "Fixing post #{post.id} with #{post.raw[0..30]}"
          raw = post.raw.gsub(preg,"#{$2}")
          user = User.find(post.user_id)
          post.revise(user,{raw: raw,edit_reason: 'remove user name from text'}, opts)
        rescue
          puts "#{post.id}--cannot save #{post.raw}. "
        end
      end
    end
  end
end