digest_custom_html을 HTML로 처리하도록 수정 (기존: digest.html.erb 오버라이딩)

요약:

디제스트(Digest)에 몇 가지 내용을 추가하려고 합니다. 예전에는 템플릿을 오버라이드하는 방식으로 처리했는데, 지금은 이유를 알 수 없게 되어 그렇게 할 수 없습니다.

템플릿 오버라이드는 나쁜 관행이기 때문에, 디제스트 내의 일부 위치는 digest_custom_html로 대체됩니다. 하지만 이 메서드는 텍스트를 삽입하므로, 해당 부분에 .html_safe를 추가해야 합니다. 적어도 하나는 잘못된 위치에 있습니다(이름에는 "above"가 들어 있지만, 실제로는 아래에 위치해 있습니다).

이 문제는 제가 PR을 작성할 수 있는 수준의 기술력이 있다고 생각하지만, 너무 간단해서 다른 사람이 처리하는 것이 더 쉬울 수도 있습니다.

더 길고 고통스러운 이야기.

저는 여기서 구현을 시도했습니다: GitHub - pfaffman/discourse-add-to-summary: Add text to summary before and after title · GitHub 하지만 더 이상 작동하지 않는 것 같습니다.

코어 변경 없이 제가 하고 싶은 것은 digest.html.erb 템플릿을 오버라이드하는 것입니다. 예전에는 app/views/user_notifications/digest.html.erb에 파일을 두면 코어에 있는 파일 대신 해당 파일이 처리되도록 할 수 있었습니다. 하지만 더 이상 그렇게 작동하지 않습니다.

이제 다음과 같은 멋진 digest_custom_html 기능이 있습니다:

민첩한 독자는 인기 있는 주제(Popular topics) 섹션이 277번째 줄보다 훨씬 앞선 78번째 줄 근처에서 시작한다는 것을 알아차릴 것입니다. 제가 잘못된 곳을 보고 있어서 아무것도 일어나지 않는다고 생각했던 데 얼마나 많은 시간을 보냈는지 정확히 알 수 없습니다. 하지만 본론에서 벗어났네요.

plugin.rb의 after_initialize에서 다음과 같이 구현하는 데 성공했습니다.

  require_dependency "user_notifications"
  module ::UserNotificationsHelperOverride
    def digest_custom_html(position_key)
      puts "doing improved the digest: #{position_key}"
      if position_key == "below_popular_topics"
        puts "doing the custom html for above_popular_topics"

        # Custom HTML for the popular topics position
        "<div class='custom-popular-topics'>MY COOOOOOOL TEXT</div>"
      else
        puts "doing the super for #{position_key}"
        super
      end
    end
  end

그리고 실제로 템플릿에서 제가 변경을 기대하는 위치에 텍스트가 추가되고 있습니다!

아쉬운 점은 HTML로 처리되지 않고 텍스트로 처리된다는 것입니다. 그래서… .

all_the_plugins를 살펴봤지만 이 코드를 사용하는 예시를 찾지 못했습니다.

다음과 같은 줄을

        <%= digest_custom_html("below_popular_topics") %>

아래와 같이 교체한다면

        <%= digest_custom_html("below_popular_topics").html_safe %>

PR이 환영받을까요?

그리고, 이 기회에 position_key의 이름이 실제 위치와 더 유사하도록 수정하는 것도 좋을까요?

PR를 만들었습니다:

digest_custom_html이 파싱할 수 있는 HTML을 생성하지 않는 것은 버그라고 판단하여, 카테고리를 변경합니다.

@martin, 부디 너그러이 봐주세요. 하지만 digest.html.erb를 마지막으로 수정한 사람이 두 해 전 당신이었거든요. 이 PR을 한번 봐주시지 않을 수 있을까요?

이 플러그인은 배포되었고, 곧 잊게 될 가능성이 높습니다. 따라서 digest_custom_html 필드를 의도된 대로 사용하려는 분이 계시다면, app.yml에 다음과 같은 코드를 추가하여 소스를 패치할 수 있습니다. 모든 것을 대체하는 정규식을 만드는 것은 귀찮아서, 제가 사용하던 것 하나만 처리했습니다. 필요에 맞게 수정해 주세요.

플러그인 내에서 템플릿을 만들어 app.yml에 포함할 수 있도록 했습니다. 이렇게 하면 YAML 블록 전체를 다루는 것보다 훨씬 간단합니다.

hooks:
  after_code:
    - replace:
        filename: "/var/www/discourse/app/views/user_notifications/digest.html.erb"
        from: 'digest_custom_html("above_footer") '
        to: 'digest_custom_html("above_footer").html_safe '

팀에게 오픈된 PR을 알리는 데 도움이 되는 태그를 사용하는 것이 의미가 있을까요?

Hey @david. 이 PR에 관심이 있는 사람이 있는지 한 번 더 확인해 볼 생각입니다. 위에서 설명한 것처럼, 코드가 의도한 대로 작동하지 않는 것이 분명하지만, 아무도 신경 쓴 적이 없습니다. 배포 시 템플릿을 패치하는 방식으로 우회해 코드를 수정하여 배포했지만, 깔끔한 해결책은 아닙니다.

메서드 오버라이드의 결과에 .html_safe를 추가할 수 있을까요? erb 템플릿에 포함될 필요가 있다고 생각하지 않습니다.

Rails와 Ember 모두에서 일반적인 목표는 "이 문자열은 HTML 안전하다"는 표시를 작성/생성 지점에 최대한 가깝게 두는 것입니다. 이를 통해 개발자가 HTML이 실제로 안전한지(즉, 모든 사용자 입력이 이스케이프되었는지)를 확인해야 한다는 점이 매우 명확해집니다.

현재 하고 계신 방식은 (테스트가 되어 있다면) 괜찮지만, 이 메서드들의 “의도된” 사용법은 아닙니다. 의도된 플러그인 API였다면 다른 것들과 함께 plugin/instance.rb에 위치했을 것입니다.

이 메서드의 의도된 사용법은 마크다운을 일치하는 번역 키에 넣는 것입니다:

(거기에도 html_safe가 있다는 점에 유의하세요 - 모든 오버라이드에서 사용해야 하는 동일한 기법입니다)

오, 정말… 할 수 있다니!!! 템플릿의 위쪽(최소 내 머리속에서는) 렌더링되는 부분에 있는 것이 아니라, 바로 거기에 넣을 수 있다는 생각은 전혀 하지 못했거든요.

이제 digest_custom이 .html_safe를 호출한다는 것을 보고 이해하게 되었어요. 원하시는 대로, 제가 살펴보고 이해하려고 했던 코드에서요. 다만, 그 특이한 괄호 없는 문법을 사용한다는 점이 (제게는) 혼란스럽습니다. (아니면, 그냥 제가 그걸 발견하지 못했을 수도 있겠네요).

글쎄요, 로케일(로컬라이제이션)에서 값들은 볼 수 있지만, 전에 하던 것처럼 템플릿을 오버라이드하는 것과 비교하면, 이 방법은 훨씬 위험성이 적은 해결책입니다.

제 길을 제대로 안내해 주셔서 정말 감사합니다!

또 다른 한 가지, 제가 좀 어리석게 느끼는 일이 있습니다. 제 digest_custom_html은 렌더링되는 사용자를 필요로 하므로, digest를 오버라이드하여 @user를 설정하고 있습니다. 이렇게 하면 제 digets_custom_html이 다이제스트의 대상 사용자에게 접근할 수 있습니다. 이 과정을 피할 수 있는 아주 쉬운 방법이 있을까요?

after_initialize do
  # Code which should run after Rails has finished booting

  # require_relative "lib/discourse_add_jobs_to_digest/user_notifications_helper_override"
  require_relative "lib/discourse_add_jobs_to_digest/engine"
  require_relative "lib/discourse_add_jobs_to_digest/job_api"

  require_dependency "user_notifications"
  module ::UserNotificationsHelperOverride
    def digest_custom_html(position_key)
      if position_key == "above_footer"
        DiscourseAddJobsToDigest::JobApi.get_jobs_html(@user).html_safe
      else
        super
      end
    end
  end

  UserNotificationsHelper.prepend(::UserNotificationsHelperOverride)

  module ::UserNotificationsOverride
    def digest(user, opts = {})
      @user = user
      super
    end
  end
  UserNotifications.prepend(::UserNotificationsOverride)
end

템플릿/메소드에서 user 변수를 사용하는 다른 부분이 보이지 않으니, 네가 한 방식이 아마도 가장 좋은 방법일 거야.

하지만 이런 종류의 오버라이드는 '지원’되지 않으며, 언제든 깨질 수 있어. 결국 문제가 생겼을 때 이를 잡아낼 수 있도록 테스트를 꼭 해두길 바란다.

확인해 줘서 고마워! 시간 내어 주셔서 감사해.

알겠어. 이건 여전히 비슷한 플러그인에 대해 내가 이전에 사용했던 전체 템플릿 오버라이드 방식보다 훨씬 낫거든.

그리고, 그래, 테스트가 있어야 해. :wink: