PostRevisor가 삭제된 주제의 게시물을 수정할 수 없음

요약

일부 게시글에서 PostRevisor를 호출하면 post_id가 nil로 설정됩니다. 제가 이상한 건가요?

답: 아닙니다. PostRevisor는 post.topic에 접근하는데, 삭제된 토픽에 있는 게시글의 경우 이는 nil입니다. 그리고 나서 post.topic을 nil로 설정하게 되고, 이는 다시 post.topic_id를 nil로 설정하게 됩니다.

PostRevisor는 다음과 같이 토픽을 가져와야 한다고 생각합니다:

  @topic = topic || Topic.with_deleted.find_by(id: post_topic_id)

다음과 같이 가져오는 대신 말입니다:

  @topic = topic
post=Post.find_by(topic_id: 179227, post_number: 12)
post.topic_id => 179227
pr=PostRevisor.new(post)
post.topic_id => nil

전체 이야기

goo.gl 링크를 수정하는 스크립트를 작업 중입니다(해당 서비스가 곧 종료될 예정이므로, 스크립트는 goo.gl 링크를 찾아 리디렉션되는 대상 URL을 가져오고, gsub로 goo.gl URL을 해당 대상 URL로 대체합니다. 대부분 잘 작동합니다).

그러나

많은 게시글에서 모든 것이 정상적으로 진행되는 것처럼 보이다가, post.acting_user가 nil이기 때문에 PostRevisor가 실패합니다. 그리고 나서, 제 rescue 블록에서 topic_id가 nil인 것으로 보이지만, post 자체는 nil이 아닙니다. 왜냐하면 여전히 post_number가 있기 때문입니다.

      begin
        puts "Revising (#{count}/#{total_posts}) https://mysite.com/t/#{post.topic_id}/#{post.post_number}"
        puts "missing topic_id for post #{post.id}" if !post.topic_id
        next if !post.topic_id
        PostRevisor.new(post).revise!(system_user, raw: new_raw, **revision_options)
      rescue => e
        puts "cannot revise (number: #{count} https://tw.forumosa.com/t/#{post.topic_id}/#{post.post_number}): #{e}"
      end
FIXING!!: https://goo.gl/maps/XaNG
B7qaZGzhBmM78 -----> https://www.google.com/maps/place/%E6%AD%A5%E9%81%93%E5%92%96%E5%95%A1%E9%A4%A8Cafe+Strada/@22.6300414,120.3153591,17z/d
ata=!3m1!4b1!4m5!3m4!1s0x346e04944a9b3471:0x520c1f01c3d62e57!8m2!3d22.6301115!4d120.3175543?shorturl=1
Revising (680/1773) https://mysite.com/t/207069/1817
cannot revise (number: 680 https://mysite.com/t//1817): undefined method `acting_user=' for nil

대부분의 게시글에서는 이 방식이 잘 작동하지만, 일부 하위 집합에서는 이와 같이 실패합니다. 그리고 코드를 수동으로 한 줄씩 실행해도 동일한 결과를 얻습니다. 하지만, pr=PostRevisor.new(post)를 실행하면 pr 레코드에 있는 post에 topic_id가 없고, 그 후 post를 검사하면 이제 topic_id가 nil로 설정되어 있는 것으로 보입니다.

1개의 좋아요

궁금한 건데, Post 모델을 직접 수정하지 않는 이유가 있나요?

gsub와 복잡한 정규식, 그리고 수많은 엣지 케이스(예: goo.gl, http://goo.gl, https://goo.gl은 수정하되 https://maps.app.goo.gl이나 https://map.goo.gl은 건드리지 말 것, goo.gl로 인해 레이트 리밋에 걸릴 수도 있는 경우 등)를 사용해 3,000개나 되는 포스트를 변경하고 있다면, 완전히 망치기 전에 원래의 포스트로 되돌아갈 수 있는 기능이 필요할 수 있기 때문입니다!

수정 내역을 살펴보고 변경 전후를 비교한 뒤 되돌릴 수 있는 것은 정말 멋진 기능이었어요. 예를 들어, 버전 관리가 없다면 https://maps.app.goo.gl/abd12 같은 링크가 https://maps.app.https://maps.google.com/;lkajw3rpoazse;flknmase;faijserfasefklasdfa처럼 엉망이 될 수 있습니다.

1개의 좋아요

_그거_는 말이 되네 :slight_smile:

1개의 좋아요

몇 년 전에 비슷한 스크립트가 있었는데, 어떤 클라이언트에게 CDCK를 실행해 달라고 요청했을 때 그들은 (내 말로 하면) "야, 수백 개의 게시글을 아무렇게나 변경하는 네 코드를 실행해 주면서 되돌릴 수도 없는 걸로 만들어? 다시 생각해 봐."라고 했습니다.

1개의 좋아요

네, 다른 방법으로는 제어된 구역에서 백업으로 실행하는 것입니다. 하지만 당신의 해결책이 마음에 듭니다.

1개의 좋아요

자, 이니셜라이저입니다:

그러니까 어떤 식으로든 포스트가 거기까지 도달하는데, post_id에는 값이 있는 반면 post에는 값이 없는 것 같네요?

이것은 재현할 수 없습니다.

마지막 post.topic_id는 nil이 아니며, 이전 결과와 동일하게 예상대로 출력됩니다.

네. 대부분의 게시물에서는 잘 작동했습니다. 일부 게시물에서 뭔가 이상한 일이 일어나고 있습니다.

[63] pry(main)> post.topic_id
=> 179227
[64] pry(main)> post.topic
=> nil
[65] pry(main)>

이런 일은 일어나지 않아야 한다고 확신합니다. :person_shrugging:

하지만 잠깐:

 Topic.find(post.topic_id)
ActiveRecord::RecordNotFound: Couldn't find Topic with 'id'=179227 [WHERE "topics"."deleted_at" IS NULL]
from /var/www/discourse/vendor/bundle/ruby/3.3.0/gems/activerecord-7.2.2.1/lib/active_record/relation/finder_methods.rb:428:in `raise_record_not_found_exception!'

게시판이 삭제된 상태입니다.

그러면 질문은 삭제된 게시판에서 게시물을 수정할 수 없는 것이 버그인 것인지 여부입니다.

아마 저는 신경 쓰지 않고, 스크립트에서 post.topic_id 대신 post.topic이 nil인지 확인하도록 할 것입니다.

1개의 좋아요

네, 외래 키에서 무결성 문제가 심각하네요!

테이블이 보통 너무 크기 때문에 일부 가드레일(음)이 꺼져 있는 것 같습니다.

아마 누군가가 destroy 대신 topic.delete를 실행한 것 같아요. 어머.

게시글을 검색할 때 Post.where("raw like '%goo.gl%'")를 사용했는데, 이는 삭제된 토픽에 있는 게시글까지 반환합니다. 그런 게시글들은 topic_id는 있지만 topic은 없습니다. Topic.find가 삭제된 토픽도 반환할 수 있는 방법이 있다고 생각합니다 (관리자는 삭제된 토픽을 볼 수 있기 때문에, 이 부분이 정말 혼란스러웠습니다. 그 작은 휴지통 아이콘은 놓치기 쉽거든요.)

그래서 이렇습니다:

deleted_topic = Topic.with_deleted.find_by(id: 123)

아마 PostRevisor를 호출하기 전에 이렇게 해서 게시글을 업데이트했어야 했을지도 모릅니다.

하지만 UX상에서는 삭제된 토픽의 게시글을 수정할 수 있으므로, 다음 코드

def initialize(post, topic = post.topic)
  @post = post
  @topic = topic
  # Topic 인스턴스가 하나만 있도록 보장
  post.topic = topic
end

은 다음과 같이 되어야 한다고 생각합니다.

def initialize(post, topic = post.topic)
  @post = post
  @topic = topic || Topic.with_deleted.find_by(id: post_topic_id)
  # Topic 인스턴스가 하나만 있도록 보장
  post.topic = topic
end

혹시 다른 사람도 이것이 버그라고 생각할 수 있으니 #contribute:bug로 옮기겠습니다.

하지만 이건 동작합니다:

        if !post.topic # 삭제된 토픽의 게시글은 topic이 없어 PostRevisor를 깨뜨림
           post.topic = Topic.with_deleted.find_by(id: post.topic_id)
           next if !post.topic
        end
        PostRevisor.new(post).revise!(system_user, raw: new_raw, **revision_options)

Rails를 크래시 시킨 게시글이 3개의 토픽에 있었습니다. 그건 포기합니다. :slight_smile:

1개의 좋아요

위에서 언급한 방법의 또 다른 버전으로, deleted_topic = Topic.with_deleted.find_by(id: 123)을 실행하여 post.topic를 업데이트하는 방식도 작동합니다.

그래도 여전히 버그처럼 보입니다. 하지만 코어가 이 문제를 관리하기 위해 다른 방식으로 처리하고 있다면, 버그가 아닐 수도 있습니다.

1개의 좋아요

아니면 누락된 Feature(그리고 따라서 spec)일까요?

온라인에서는 삭제된 Topic의 Posts에 대해 이 기능이 절대 호출되지 않나요?

다만, 이 부분을 적절히 처리해야 한다는 점에는 동의합니다. :thinking:

아마도

그들은 삭제된 토픽의 게시물에서도 작동하도록 허용하기 위해 분명 무언가를 하고 있는 것 같습니다. 아마도 저와 비슷한 방식으로요.

네. 그러한 결정을 내리는 사람이 적절하다고 생각되면 이 이슈를 #dev로 다시 이동시킬 수 있습니다.

이 주제는 마지막 답변 후 30일이 지나면 자동으로 닫힙니다. 더 이상 답변을 남길 수 없습니다.