Discourse 플러그인과 Rails 6 config/initializers 관련 질문

디스코urs 플러그인 전문가분들께,

디스코urs 플러그인과 Rails 초기화 프로그램(Initializers)에 대해 간단한 질문이 있습니다.

디스코urs 플러그인에 "config"라는 이름의 디렉터리가 있고, 그 아래 "initializers"라는 하위 디렉터리가 있을 때, 디스코urs Rails 앱이 Rails 6에서처럼 “initializers” 디렉터리 아래에 있는 모든 플러그인 초기화 파일들을 읽어드리는지 궁금합니다.

이렇게 질문하는 이유는, 현재 클라이언트를 위해 순수 Rails 6 애플리케이션(EmberJS나 기타 JS 프레임워크 없이)을 처음부터 작성하고 있는 중인데, 초기화 프로그램 디렉터리 아래에 다음과 같은 구조의 디렉터리가 있기 때문입니다:

./config/initializers/client/

… 그리고 클라이언트 고유의 모든 초기화 프로그램은 “client” 하위 디렉터리에 있습니다.

Rails 6는 표준 초기화 프로그램 디렉터리 아래에 있는 모든 파일(하위 디렉터리 포함)을 읽기 때문에, 유사한 초기화 프로그램 디렉터리 구조를 가진 디스코urs 플러그인도 Rails 6처럼 동작하여 플러그인 내의 모든 초기화 프로그램을 다음과 같은 방식으로 읽어드리는지 궁금합니다:

./plugins/my_plugin/config/initializers/myclient/
      client_initializer1.rb
      client_initializer2.rb
      client_initializer2.rb

… plugin.rb 파일에서 이러한 에셋을 등록하지 않고서도요?

감사합니다!

P.S. GitHub에서 약 10개의 디스코urs 플러그인을 살펴봤는데, 확인한 플러그인 중 config 디렉터리 아래에 초기화 프로그램이 있는 것은 없었습니다. 그래서 이 질문을 게시하게 되었습니다 (현재 제 Rails 개발 환경은 디스코urs가 아닌 클라이언트용으로 설정되어 있습니다).

일반적인 Rails Engine과 Discourse 플러그인의 차이점도 알고 싶습니다. 소스 코드를 살펴봤는데, 크게 이해가 가지 않네요.

이 링크 Ruby on Rails Guides: Configuring Rails Applications 를 대충 살펴본 결과, plugin.rb이 이와 비슷한 역할을 하는 것 같습니다. 상당한 규모의 플러그인들은 로직 자체보다는 로직의 진입점(entry point)으로 plugin.rb을 사용하는 경우가 대부분입니다.

네, 요즘 그 Rails 가이드를 자주 읽고 있습니다. 기본적으로 Rails 6의 설정 방식에는 "괜찮다"고 생각합니다(아직 학습 중이지만, 매일 조금씩 더 익숙해지고 있습니다).

다만, Discourse 플러그인(초기화 파일)에서도 동일한 구조를 사용할 수 있는지 궁금합니다. Rails 6처럼 서브디렉터리의 초기화 파일도 Rails가 읽어들이는지, 아니면 plugin.rb에서 플러그인 초기화 파일 디렉터리를 설정 “자산(asset)”(더 좋은 표현이 없어서 이렇게 표현합니다)으로 등록해야 하는지요.

어제 Rails 플러그인에 대해 조사하면서 비교도 조금 해 보았습니다.

제가 아는 한, Discourse는 plugin.rb를 제외한 어떤 Ruby 파일도 읽지 않습니다. js와 config 파일은 읽습니다. 모든 Ruby 파일은 plugin.rb에서 require해야 합니다.

저도 그렇게 생각합니다.

필요한 추가 Ruby 파일은 plugin.rb 내에서 로드하면 됩니다.

아래는 Follow 플러그인의 예시입니다:

GitHub에서 여러 Discourse 플러그인을 살펴본 결과, 제 생각도 비슷합니다.

Discourse가 plugin.rb만 읽고, 이니셜라이저와 같이 필요할 수 있는 다른 Ruby 파일들을 모두 plugin.rb에서 로드해야 하는 것 같습니다.

하지만, 제 생각이 틀렸기를 바랐습니다. Rails 6가 얼마나 훌륭한지 많이 익숙해져서, Discourse 플러그인과 Rails의 “더 깊은” 통합이 있을 수도 있다고 기대했거든요.

확인해 주셔서 감사합니다.

plugin.rb에서도 초기화(initializer) 코드를 require할 수 있는 방법이 있어야 한다고 생각합니다.
plugin.rb에서 이렇게 시도해 보셨나요?

Rails.application.config.before_initialize do
  # 초기화 코드를 여기에 작성합니다
end

네, plugin.rb에서 초기화자를 호출하고 로드하는 것은 문제가 되지 않습니다.

질문을 드린 이유는 현재 기업용 대규모 Rails 6 애플리케이션을 개발 중인데, 수십 년간 사용되어 온 레거시 백오피스 스크립트를 Rails 애플리케이션으로 전환하는 작업을 하고 있기 때문입니다. 초기화자를 추가하기 위해 config/initializers (Rails) 아래에 하위 디렉터리를 만들고, 모든 초기화자 파일을 포함시키기 위해 별도의 코드를 작성할 필요 없이 깔끔하게 포함됩니다.

답변해 주신 모든 분들께 감사드립니다. 정말 감사드립니다!

네, Rails의 이러한 장점이 Discourse 플러그인으로 이식되면 좋겠습니다. 개인적으로 가장 좋아하는 기능 중 하나는 서버를 재시작하지 않고 Ruby 파일을 실시간으로 다시 로드할 수 있는 것입니다.

이것은 reloadable_patch를 위해 존재합니다. ruby 클래스 변경 사항을 reloadable_patch로 감싸면 라이브 리로드가 작동해야 합니다!

개발 중에 plugin.rb 파일 전체를 이 안에 감싸도 될까요? 더 진지하게 말하자면, 이 방법으로 얼마나 멀리까지 갈 수 있을까요?

또한, 새 파일을 추가하거나 yml 파일을 변경하는 경우 서버 재시작이 필요하겠죠?

모든 것에 대해 reloadable_patch를 사용해야 할 필요는 없습니다. 플러그인 개발을 쉽게 만들기 위해 instance.rb에 정의된 많은 메서드들이 내부적으로 reloadable_patch를 사용하며, add_to_serializer도 그중 하나입니다.

이상적으로는 플러그인 API가 충분히 잘 설계되어 있어, 과도한 reloadable_patch를 사용할 필요가 없어야 합니다.

네, 맞습니다. yml 파일 변경에 대해 뭔가 개선할 수 있는 부분이 있을지 궁금합니다. 그 부분은 개인적으로 좀 불편하게 느껴집니다.

맞는 말씀입니다. reloadable_patch가 라이브 리로딩을 지원하기 위해 사용되는 것인 줄 몰랐습니다. Discourse가 이미 사용하고 있는 경우를 제외하고도, 이 방법을 활용할 수 있는 많은 사용 사례가 떠오릅니다. 예를 들어,

reloadable_patch do
 require 'x/y/z'
end

또는 몽키 패치(monkey patch) 등이 있습니다.

reloadable_patch가 함수 내부에서 호출되므로, 이러한 함수를 제거하거나 추가하는 것은 여전히 문제가 될 것 같습니다.

어쨌든 큰 도움이 됩니다.

yml 파일 변경 사항에 대한 라이브 리로딩을 조사해 보았는데, 실제로 작동합니다. 플러그인이 reloadable_patch를 올바르게 사용하지 않으면 이 라이브 리로딩이 깨질 수 있지만, 모든 플러그인이 안전하게 리로드될 수 있도록 하면 yml 변경 사항도 함께 리로드됩니다.

다만, reloadable patch를 사용하는 정확한 단계를 설명해 주시면 정말 도움이 될 것 같습니다.

아직까지 큰 성과 없이 시도해 본 내용은 다음과 같습니다(확실히 뭔가 놓치고 있는 것 같습니다).

after_initialize do
    add_to_serializer(:topic_view, :check, false) do
        puts 'nocheck'
    end
end

서버가 시작되고 nocheck를 check로 변경한 후에도, topic 라우트를 다시 로드하면 여전히 nocheck가 출력됩니다.

after_initialize do
    reloadable_patch do |plugin|
        puts 'nocheck'
    end
end

puts 아래 문자열을 변경한 후 어떤 페이지든 다시 로드해도 여전히 같은 문제입니다.

아마도 이전 게시물들에서 여러분을 오해하게 만들었나 봅니다. reloadable_patch는 Discourse 개발에 유용하지만, @david가 그 용도를 아주 잘 설명해 주셨습니다:


plugin.rb의 after_initialize 블록 안에 있는 코드는 애플리케이션 시작 시에만 로드되고, 이후의 리로드 시에는 로드되지 않습니다.

따라서 유저 시리얼라이저에 무언가를 추가하고 싶다고 가정해 보겠습니다. 일반적인 동작은 다음과 같습니다:

시작 시:

  • Discourse가 user_serializer.rb를 로드합니다.
  • user_serializer에 오버라이드가 포함된 plugin.rb를 Discourse가 로드합니다.

리로드 시:

  • Discourse가 user_serializer.rb를 다시 로드합니다.
  • (plugin.rb 패치는 다시 로드되지 않아 플러그인 오버라이드가 사라집니다)

우리의 reloadable_patch 시스템을 사용하면:

시작 시:

  • Discourse가 user_serializer.rb를 로드합니다.
  • Discourse가 plugin.rb를 로드하고 user_serializer에 대한 reloadable_patch를 등록합니다.
  • 리로드 가능한 패치들이 실행됩니다.

리로드 시:

  • Discourse가 user_serializer.rb를 다시 로드합니다.
  • 리로드 가능한 패치들이 실행됩니다.
  • (좋아요, 플러그인 오버라이드가 여전히 작동합니다)

이는 Discourse와 직접적인 관련이 없는 Rails의 핵심 속성에 대한 설명입니다.

Rails에서는 모든 초기화자(initializer)가 Rails가 시작될 때만 로드되므로, 초기화자에 기반하여 코드를 실행하는 플러그인(예: “after_initializer”)은 Rails가 시작되거나 재시작될 때만 실행됩니다(잘못된 부분이 있다면 지적해 주세요).

이 주제에 익숙한 이유는 현재 클라이언트를 위해 Rails 애플리케이션을 개발 중이며, 다양한 작업(불리언 값, 정적 배열 등)을 위해 수많은 초기화자를 작성했기 때문입니다. 각 경우에서 Rails 초기화자의 코드를 변경하면, 초기화자의 새 값을 Rails 애플리케이션에 반영하려면 Rails를 재시작(“개발 환경에서는 Control c, rails s”)해야 합니다.

잘못된 부분이 있다면 꼭 지적해 주세요! 감사합니다.

때로는 “Discourse가 이렇게 한다” 또는 "Discourse가 저렇게 한다"라고 쓸 때, 실제로 설명하려는 것이 Discourse 자체와는 무관한 Rails의 핵심 기능이나 속성인 경우가 있습니다. 이는 주로 Rails 애플리케이션 전반이 아닌 Discourse 플러그인 개발에 집중하는 개발자, 특히 Discourse 플러그인 개발자에게는 다소 혼란을 줄 수 있습니다.

Rails 6는 config/initializers 디렉터리(및 모든 config/initializers 하위 디렉터리)의 모든 파일을 오직 Rails가 시작될 때만 읽습니다. 마찬가지로(플러그인에 대해서는 100% 확신은 없으나) after_initialize에 의존하는 코드가 포함된 Discourse 플러그인은 Rails 부팅 프로세스의 근본적인 속성인 관계로, Rails가 시작되거나 재시작될 때만 Rails 애플리케이션에 의해 로드됩니다(확인해 주실 분 계신가요?).

제 추측으로는 여기에서 Rails를 다루는 분들은 이미 이 사실을 알고 있을 것입니다. 저는 이러한 세부 사항들을 이제 막 알게 되고 있으며(전문가가 되기까지는 아직 갈 길이 멀다고 느낍니다), 요즘 매일 Rails 애플리케이션 작업을 하고 있기 때문입니다. 참고로, LAMP 개발 배경에서 온 저로서는 Rails가 훨씬 더 낫다(작업하기 쉽고 재미있다)고 생각하여, 10년 전에 Rails를 시작했으면 좋았을 것이라 생각합니다.

올해 저는 Rails의 열렬한 팬이 되었으며, 저를 그 길로 이끌어 준 Discourse에 매우 감사드립니다.

다시 한번, 잘못된 부분이 있다면 지적해 주세요! 감사합니다. 저는 Rails에 대해 더 전문적으로 되고자 노력하고 있습니다.

네! 맞습니다. Rails 초기화 프로그램(initializers)에 대한 이해도 정확합니다.

저 역시 Rails의 열렬한 팬입니다 :slight_smile: