@angus님의매우 유용한 가이드를 참고하여 토픽과 그룹에 커스텀 필드를 추가하는 데 성공했습니다.
커스텀 필드를 성공적으로 추가한 후, 해당 커스텀 필드를 기반으로 항목을 검색하는 (효율적인) 방법이 있을까요?
예를 들어, 토픽에 fun_level이라는 커스텀 필드를 추가했다고 가정해 보겠습니다. 그러면 이제 제 플러그인을 통해 topic.fun_level(문자열) 필드가 추가된 상태입니다.
이제 fun_level이 "super-duper-fun"인 모든 토픽의 목록을 검색하고 표시하고 싶습니다.
어떻게 해야 할까요?
특정 태그가 있는 모든 토픽을 검색하려면 ajax('/tags/tag-name.json').then(function(result))...와 같은 방법을 사용할 수 있습니다. 이 설명 게시물에서 사용된 방식과 같습니다.
방금 만든 커스텀 필드의 경우, 이 방법은 사용할 수 없을 것 같습니다(생각하기에). 이는 커스텀 필드를 위한 컨트롤러(예: fun_level 컨트롤러)를 생성해야 하기 때문입니다. 이 컨트롤러에는 :fun_level 파라미터 값과 fun_level이 일치하는 모든 토픽을 검색하는 show 메서드가 포함되어야 합니다.
커스텀 필드를 기반으로 주제를 가져오는 한 가지 방법을 알아냈습니다. 바로 검색을 수행하고 결과를 가져오는 방식입니다.
예를 들어, 어딘가에 "searchForTopics()"라는 액션이 있는 버튼이 있다고 가정하고, 커스텀 필드 fun_level이 "super-duper-fun"인 주제를 가져오려고 한다고 생각해 보겠습니다:
(이것은 초기화 프로그램과 같은 js.es6 코드에 들어가게 됩니다):
import Topic from 'discourse/models/topic'; //아래 코드에 따라 필수적이지는 않지만, 수행하려는 다른 관련 액션에는 관련이 있을 수 있습니다.
import { ajax } from 'discourse/lib/ajax';
export default {
actions: {
checkTopic(){
let custom_field_value = 'super-duper-fun'
let searchTerm = 'fun_level:' + custom_field_value
let args = { q: searchTerm }
ajax("/search", { data: args }).then(results => {
let topics = results.topics
topics.forEach(topic => {
//주제 이름 목록을 가져오기 위해서
console.log('topic name = ')
console.log(topic)
})
})
}
}
}
이것은 작동합니다. 하지만 이것이 가장 효율적인 방법일까요?
예를 들어, 특정 태그와 일치하는 주제를 표시하기 위해 해당 태그 페이지로 이동할 때 디스코스가 사용하는 방법과 비교했을 때, 검색을 사용하는 이 방법이 그만큼 효율적인가요?
클라이언트: api.addDiscoveryQueryParam을 사용하여 토픽 쿼리 매개변수를 추가합니다.
서버: TopicQuery 클래스(lib/topic_query 참조)에서 add_custom_filter를 사용하여 해당 매개변수로 토픽 쿼리를 필터링합니다.
add_custom_filter 콜백은 다음과 유사한 형태가 됩니다.
::TopicQuery.add_custom_filter(:field_name) do |topics, query|
if query.options[:field_name]
topics.where("topics.id in (
SELECT topic_id FROM topic_custom_fields
WHERE (name = 'field_name')
AND value = '#{query.options[:field_name]}'
)")
else
topics
end
end
수정: api.addDiscoveryQueryParam에 대해 더 자세히 살펴본 결과, 이제 일반적인 개념을 파악한 것 같습니다:
커스텀 필드 fun_level = super-duper-fun인 모든 토픽을 프로그래매틱하게 가져오고 싶습니다. 컨트롤러 메서드로 이를 수행할 수 있지 않을까요? (아직 그 방법을 알아내는 중).
대안으로, 커스텀 필드 fun_level=super-duper-fun 기준으로 모든 토픽을 검색하는 ajax("/search")를 사용하는 방법이 있습니다. 하지만 커스텀 필드를 생성하는 것만으로는 이를 활성화할 수 없습니다. fun_level 커스텀 필드를 검색할 수 있는 필드 중 하나로 만들어야 합니다(특정 카테고리, 태그 등으로 검색할 수 있는 것과 마찬가지로요). 이는 자동으로 이루어지지 않습니다.
어떤 방식으로든, plugin.rb의 TopicQuery와 혼합된 js 파일의 api.addDiscoveryQueryParam이 이를 수행하는 데 필요합니다. 하지만 솔직히 아직 작동하게 만들지 못했습니다. 이 메서드들을 사용하는 몇 가지 플러그인을 봤지만, 그들이 어떻게 이를 "성공적으로 마무리"하는지 파악하지 못했습니다. 추가 코드가 필요하다고 생각하는데, 아직 찾지 못했습니다.
이 메서드들로부터 실제로 커스텀 필드를 검색 조건으로 사용할 수 있게 만드는 과정은 어떻게 되는 것인가요?
이전 답변
감사합니다, @angus. 명확히 하자면, 목표는 사용자가 검색창에 검색 값을 수동으로 입력하게 하는 것이 아닙니다. 목표는 특정 커스텀 필드를 기준으로 토픽을 프로그래매틱하게 가져오는 것입니다. 예를 들어, 사용자가 /fun_levels/super-duper-fun 페이지로 이동하면 fun_level 필드가 'super-duper-fun’인 모든 토픽이 로드되어야 합니다.
api.addDiscoveryQueryParam이 그 목적을 위한 것인가요?
여기와 같은 예시를 살펴보면, addDiscoveryQueryParam이 실제로 토픽을 가져오는 데 어떻게 작동하는지 확실하지 않습니다(그 메서드를 호출한다고 해서 파싱할 수 있는 결과가 반환된다고 생각하지 않습니다).
아마도 사용자가 검색창에서 해당 용어로 수동 검색할 수 있도록 허용하기 위한 것일까요? 제가 의도하는 상황은 아닙니다. (무언가를 놓치고 있을 수도 있습니다.)
이전에 ajax("/search...") 사용에 대해 언급한 이유는, 지금까지 제가 생각해 낸 토픽을 반환하는 가장 좋은 방법이기 때문입니다. 하지만 더 효율적인 방법이 있는지 궁금합니다. 예를 들어, 모델과 컨트롤러 메서드를 설정하여 태그의 /tags/:tag-name처럼 토픽이 자동으로 표시되도록 하는 방식도 포함합니다. (이는 더 복잡하므로 피하고 싶지만, 최선의 방법이라면 고려해 보겠습니다.)
안녕하세요, @angus. 목표는 사이드바 검색에 쿼리를 추가하는 것이 아닙니다(그것도 좋겠지만, 여기서는 목표가 아닙니다). 목표는 사용자가 페이지를 방문할 때 사용자 정의 필드를 기반으로 주제를 프로그램 방식으로 주제 목록에 로드하는 것입니다. 템플릿/컴포넌트(즉, 뷰) 부분은 파악한 것 같습니다. 이제 주제를 로드할 로직을 파악하는 중입니다.
검색에 대해 언급한 이유는, 페이지 방문 시 ajax("/search")를 custom_field=value로 실행하는 것이 주제를 로드하는 깔끔한 방법일 수 있겠다는 생각이 들었기 때문입니다. 하지만 단순히 가장 잘 작동하는 방법을 찾고 있을 뿐입니다.
자세한 내용:
제 경우, 첫 번째 목표는 제가 새로 만든 템플릿 페이지(/fun_levels/:fun_level 경로)로 사용자를 이동시키고, fun_level 사용자 정의 필드가 :fun_level과 일치하는 모든 주제를 로드하는 것입니다.
템플릿을 생성하고 해당 경로에서 로드하는 방법은 별도로 파악했습니다. 이제 페이지에 있는 주제 목록 컴포넌트에 일치하는 주제를 프로그램 방식으로 로드하고 싶습니다.
이상적으로는 새로운 “fun_level” 모델을 생성해야 하는 것을 피하고 싶습니다(아직 생성하지는 않았습니다). 이렇게 하면 구현이 더 직관적이고 빠를 것 같습니다. 하지만 불가피하게 성능 면에서 훨씬 유리하다면(이 페이지는 자주 사용될 것입니다) 그렇게 하는 것도 고려할 수 있습니다.
사이드바 검색 옵션에 "fun_level"을 추가하는 방법도 알면 좋겠습니다. 이 기능도 원하기 때문입니다. 그리고 사용자 정의 필드를 기반으로 주제를 로드하는 가장 좋은 방법이 사용자 정의 필드를 검색 옵션에 추가한 뒤, 쿼리를 "fun_level: super-duper-fun"으로 하여 ajax("/search")를 호출하는 것일 수도 있습니다.
따라서 검색 관련 부분이 여기서 중요할 수 있습니다. 하지만 현재 주된 과제는 사용자가 해당 페이지를 방문할 때 사용자 정의 필드를 기반으로 페이지에 주제를 로드하는 것입니다.
실질적으로 다릅니다. 하지만 여기서 초점은 커스텀 필드 값(예: fun_level = ‘super-duper-fun’)을 가진 토픽을 페이지에 삽입하려는 {{topic-list topics=selectedTopics showPosters=true}} 컴포넌트에 어떻게 로드하는지입니다.
토픽 리스트를 다룰 때 핵심은 기존 Discourse 탐색 구조를 확장할지, 아니면 자체적으로 만들지에 따라 구현 방식이 달라집니다. 여러분이 하려는 것과 관련된 많은 질문들이 있지만, 여기서는 생략하겠습니다.
탐색 구조를 확장하지 않는다면, 위에서 제안한 내용의 첫 번째 부분은 수행하지 않아도 됩니다. 하지만 두 번째 부분, 즉 TopicQuery에 커스텀 필터를 추가하는 것은 여전히 수행해야 합니다. 또한 list_controller.rb에 매핑된 엔드포인트로 AJAX 호출을 하는 클라이언트 측 라우트도 필요합니다. config/routes.rb에서 list#을 검색하면 리스트 컨트롤러 라우트를 찾을 수 있습니다.
Discourse 탐색에서 사용하는 것과 동일한 토픽 리스트 엔드포인트를 사용해야 합니다. 이렇게 하면 페이지네이션(토픽 리스트 컴포넌트의 스크롤 시 로드에 의해 처리됨), 권한 처리 및 기타 많은 기능을 기본적으로 활용할 수 있습니다.
커스텀 필드 값과 일치하는 주제만 가져오는 가장 쉬운 방법이 무엇이든 상관없이 기꺼이 따르겠습니다. 현재 /fun_levels/:fun_level 경로에 {{topic-list}} 컴포넌트를 로드하는 작동 중인 루트/경로/템플릿이 이미 있습니다. 다시 말해, 그 방식이 해당 커스텀 필드 값을 검색하는 것(ajax(/search))이라면 그것도 괜찮습니다. 점점 더 그것이 가장 직관적인 방법이라고 생각하고 있습니다. 다만 아직 제대로 작동하게 만들지 못했습니다.
그리고 명확히 하자면, 제 현재 방법은 다음과 같습니다.
ajax로 주제를 가져옵니다 (올바른 엔드포인트가 무엇인지, 또는 해당 엔드포인트를 어떻게 설정해야 하는지 파악하는 것이 핵심입니다),
결과를 파싱하고,
component.set('showTopics', parsed-result)를 수행하여 주제를 {{topic-list topics=showTopics}}에 로드합니다.
이 방식은 제게 다소 추상적으로 느껴집니다. list_controller의 def topics_by 같은 메서드들을 볼 수 있지만, 그 중 하나의 메서드를 가져와 커스텀 필드 값에 기반하여 주제를 반환하도록 수정하는 방법은 무엇인가요?
여러 가지 이유로 이 방식을 권장하지 않습니다. 미스터리하게 들릴 수 있지만, 그 이유를 완전히 설명하려면 지금 제 시간보다 더 많은 시간이 필요합니다.
가장 쉬운 방법은 목록 컨트롤러의 기존 엔드포인트 중 하나를 사용하는 것입니다. 그것들은 이미 주제 목록을 제공하도록 설정되어 있습니다. routes.rb에서 찾을 수 있지만, 간단히 말하면 /latest, /top 등의 필터입니다. 커스텀 필터가 포함된 목록의 경우, 다음과 같은 쿼리 매개변수를 사용해야 합니다.
/latest?fun_level=5
커스텀 필터를 사용하면 됩니다. list_controller.rb에서 TopicQuery 클래스를 따라가며 어떻게 작동하는지 확인할 수 있습니다. 예를 들어, 컨트롤러에 커스텀 필터를 지원되는 매개변수로 추가합니다. 미스터리하게 느껴지는 이유는, 해당 컨트롤러와 클래스가 페이지네이션과 다양한 필터 등 많은 일을 대신 처리해주기 때문입니다. 다른 방식으로 구현한다면 이러한 것들을 수동으로 설정해야 합니다.
(참고로 /search를 사용하지 않는다는 전제 하에) 의미상으로는 타당한 "다른 방식"은, list_controller.rb가 TopicQuery 클래스를 사용하는 것처럼, 해당 클래스를 사용하는 자체 전용 컨트롤러를 해당 라우트용으로 설정하는 것입니다. 어쨌든 완전히 새로운 라우트를 추가하는 경우 Rails 컨트롤러를 생성해야 하므로, 이는 또 다른 가능한 접근 방식입니다. 다만 이 경우 페이지네이션 등을 스스로 처리해야 합니다. 이 방식을 사용하는 경우에도 여전히 커스텀 필터를 사용해야 합니다.
이 중 일부가 불투명하게 느껴질 수 있다는 것을 이해합니다. 그러나 여기서는 복잡한 기능을 다루고 있습니다. 이를 완전히 설명하려면 10부작 코스를 작성해야 할 것입니다. 실제로 곧 그렇게 할 수도 있습니다
업데이트: 이제 (대부분) 작동하는 것 같습니다(!). 이제 커스텀 필드 값에 해당하는 “최신” 주제들이 표시됩니다. (config/routes.rb 파일에서 가장 합리적으로 느껴지는 메서드가 #latest였기 때문입니다).
실제로 해당 커스텀 필드 fun_level 값을 가진 모든 주제가 페이지에 로드되는 것이 중요합니다. 이를 위해 추가로 해야 할 일이 있을까요?
참고로, 제 기록을 위해 그리고 다른 분들에게 도움이 될 수 있도록 코드를 공유합니다:
– :fun_level 커스텀 필드를 생성했습니다. 그리고:
plugin.rb
TopicQuery.add_custom_filter(:fun_level) do |topics, query|
if query.options[:fun_level]
topics.where("topics.id in (
SELECT topic_id FROM topic_custom_fields
WHERE (name = 'fun_level')
AND value = '#{query.options[:fun_level]}'
)")
else
topics
end
end
/connectors/my-plugin-outlet/fun-level.js.es6 (해당 페이지로 이동할 때 활성화되는 자바스크립트 파일입니다. 따라서 이 자바스크립트는 초기화 파일이나 플러그인 아웃렛에 연결된 커넥터에 있을 수 있습니다. 커넥터와 함께 사용되는 코드를 선호하므로, 여기서는 setup 컴포넌트를 사용하겠습니다):
const ajax = require('discourse/lib/ajax')
export default {
setupComponent(args, component) {
let parsedResultArray = []
var endPoint = '/latest?fun_level=' + funLevel //funLevel = params에서 가져온 값을 가진 변수
ajax(endPoint).then(function (result) {
console.log('topic list result for topics matching that fun level = ')
console.log(result.topic_list.topics)
//결과를 파싱하고 parsedResultArray에 로드
component.set('showTopics', parsedResultArray
})
}
}
이제 해당 주제들이 my-plugin-outlet을 통해 템플릿에 배치된, 해당 컴포넌트에 있는 {{topic-list topics=showTopics}}에 로드됩니다.