Получить темы на основе пользовательского поля?

Используя очень полезное руководство от @angus, мне удалось добавить пользовательские поля к темам и группам.

После успешного добавления пользовательского поля существует ли (эффективный) способ получать элементы на основе этого поля?

Например, предположим, что я добавил пользовательское поле fun_level к темам. Теперь у меня есть поле topic.fun_level (строка), добавленное через мой плагин.

Теперь я хочу получить и отобразить список всех тем, где fun_level равен «super-duper-fun».

Как мне это сделать?


Если мы хотим получить все темы с определённым тегом, мы можем использовать ajax('/tags/tag-name.json').then(function(result))..., как показано в этом поясняющем посте.

Для только что созданного пользовательского поля такой возможности, вероятно, не будет (я так думаю). Это потребовало бы создания контроллера для пользовательского поля (например, контроллера для fun_level с методом show, который каким-то образом получает все темы, где fun_level равен значению параметра :fun_level, или что-то в этом роде).

Думаю, существует более прямой способ?

Кажется, я нашел один способ получать темы на основе пользовательского поля: искать их и извлекать результаты.

Вот пример. Представьте, что у вас есть кнопка с действием 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)
                })
            })
        }
    }
}

Это работает. Но является ли это наиболее эффективным способом?

Например, так же ли эффективен этот метод с использованием поиска, как метод, который Discourse использует для отображения тем, соответствующих определённому тегу, при переходе на страницу этого тега?

Чтобы сделать это, выполните следующие шаги:

  1. На стороне клиента: добавьте параметр запроса темы, используя api.addDiscoveryQueryParam

  2. На стороне сервера: отфильтруйте запросы по теме с помощью параметра, используя add_custom_filter в классе TopicQuery (см. lib/topic_query)

Обратная связь 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. Думаю, для этого может подойти метод контроллера? (все еще разбираюсь).

Альтернативный вариант — выполнить поиск через ajax(“/search”), где я ищу все темы по пользовательскому полю fun_level=super-duper-fun. Однако создания пользовательского поля недостаточно для включения такой возможности. Мне нужно сделать так, чтобы поле fun_level стало одним из полей, по которым можно выполнять поиск (точно так же, как можно искать по определенным категориям, тегам и т.д.), и это не происходит автоматически.

Для этого каким-то образом требуется использование api.addDiscoveryQueryParam в JS-файле в сочетании с TopicQuery в plugin.rb. Но, честно говоря, у меня пока не получается заставить это работать. Я видел плагины, использующие эти методы, но не смог разобраться, как они “доводят дело до конца”. Думаю, нужен дополнительный код, но я его еще не нашел.

Как перейти от этих методов к тому, чтобы пользовательское поле стало доступным как поисковый термин?

Предыдущий ответ

Спасибо, @angus. Уточню: цель не в том, чтобы пользователи вручную вводили значения поиска в поле поиска. Цель — программно получать темы на основе определенного пользовательского поля. Например, пользователь заходит на страницу /fun_levels/super-duper-fun и загружает все темы, где поле fun_level = ‘super-duper-fun’.

Для этой цели подходит api.addDiscoveryQueryParam?

Просматривая примеры, например здесь, я не понимаю, как addDiscoveryQueryParam работает для фактического получения тем (мне кажется, вызов этого метода не возвращает результаты, которые можно обработать).

Возможно, он предназначен для того, чтобы позволить пользователю вручную искать по этому термину в поле поиска? Это не тот сценарий, который я планирую. (Возможно, я что-то упускаю).

Ранее я упоминал использование ajax(“/search…”), так как это лучший вариант, который я пока придумал для получения тем, но мне интересно, есть ли более эффективный способ, даже если для этого потребуется создать модель и метод контроллера для автоматического отображения тем, как это делается для тегов вида tags/:tag-name (это более сложный вариант, поэтому я надеюсь его избежать, но если это лучший путь — я рассмотрю такую возможность).

Лучший подход здесь зависит от вашей конечной цели.

Как вы представляете себе работу этого? Как вариант в боковой панели на странице /search?

Привет, @angus. Цель не в том, чтобы добавить запрос в боковую панель поиска (это было бы здорово, но не является основной задачей). Задача состоит в том, чтобы программно загружать темы на основе пользовательского поля в список тем при посещении пользователем страницы. Я думаю, что разобрался с частью шаблона/компонента (то есть представления). Теперь пытаюсь понять логику загрузки тем.

Причина, по которой я упомянул поиск, в том, что я подумал: выполнение ajax("/search") с параметром custom_field=value при посещении страницы может быть чистым способом загрузки тем. Но я просто пытаюсь найти наиболее эффективный способ.


Подробнее:

В моём случае первая цель — чтобы пользователь переходил на новый шаблон страницы, который я создал по новому пути (/fun_levels/:fun_level), и загружались все темы с пользовательским полем fun_level, совпадающим с :fun_level.

Отдельно я уже разобрался, как создать шаблон и загрузить его по указанному пути. Теперь я хочу программно загрузить соответствующие темы в компонент списка тем, который у меня есть на странице.

В идеале я хотел бы избежать создания новой модели fun_level (которую я ещё не делал), чтобы всё оставалось проще и быстрее в реализации. Но я открыт к этому, если в итоге это окажется значительно более производительным (эта страница будет использоваться очень часто).


Также было бы полезно узнать, как добавить возможность выбора fun_level в боковой панели поиска — я бы, скорее всего, хотел это реализовать. Возможно, лучший способ загрузки тем на основе пользовательского поля — добавить это поле в параметры поиска, а затем вызвать ajax("/search") с запросом вида fun_level: super-duper-fun.

Так что вопросы, связанные с поиском, могут оказаться важны. Но главная задача сейчас — загружать темы на странице на основе пользовательского поля при посещении пользователем этой страницы.

Должна ли эта страница со списком тем ощущаться похожей на существующие страницы списков тем в Discourse или она существенно отличается?

Существенно отличается. Но здесь речь идет о том, как загрузить темы с пользовательским значением поля (например, fun_level = ‘super-duper-fun’) в компонент {{topic-list topics=selectedTopics showPosters=true}}, который я вставляю на страницу.

Когда вы работаете со списками тем, вопрос заключается в том, расширяете ли вы существующую структуру обнаружения Discourse или создаёте свою — это меняет подход к реализации. Здесь мы опускаем множество смежных вопросов, связанных с тем, что вы хотите сделать.

Так что, если вы не расширяете структуру обнаружения, вам не нужно выполнять первую часть того, что я предлагал выше. Однако вторую часть всё же стоит сделать: добавить пользовательский фильтр в TopicQuery. Также вам понадобится клиентский маршрут с AJAX-запросом к конечной точке, сопоставленной с list_controller.rb. Маршруты контроллера списков можно найти в файле config/routes.rb, выполнив поиск по строке list#.

Вам следует использовать тот же конечный пункт для списков тем, что и в механизме обнаружения Discourse. Тогда вы получите «из коробки» такие функции, как пагинация (обрабатываемая функцией загрузки при прокрутке в компоненте списка тем), управление правами доступа и многое другое.

Итак, вам понадобятся:

  1. Файл plugin.rb, содержащий пользовательский фильтр
  2. Файл клиентского маршрута
  3. Клиентский шаблон

Спасибо за информацию.

Мне подходит любой способ, который проще всего, чтобы просто получать темы, соответствующие значению пользовательского поля. У меня уже настроен рабочий маршрут/путь/шаблон по адресу /fun_levels/:fun_level, который загружает компонент {{topic-list}}. Опять же, если этот способ подразумевает поиск по значению пользовательского поля (ajax(/search)), то это тоже сработает. Я всё больше склоняюсь к тому, что это самый прямой путь. Просто я ещё не смог заставить это работать.

И для уточнения: мой текущий метод заключается в том, чтобы

  1. получить темы через ajax (мне нужно только выяснить, какой правильный endpoint и как его настроить — это ключевой момент),
  2. распарсить результат и
  3. выполнить component.set('showTopics', parsed-result), чтобы загрузить темы в {{topic-list topics=showTopics}}.

Этот способ кажется мне немного загадочным. Я вижу методы в list_controller, например def topics_by, но как мне взять один из этих методов и изменить его так, чтобы он возвращал темы на основе значения пользовательского поля?

Я бы не советовал этого делать по нескольким причинам. Извините за загадочность, но сейчас у меня нет времени подробно объяснить, почему.

Самый простой способ — использовать одну из существующих конечных точек в контроллере списков. Они уже настроены для выдачи списков тем. Вы можете найти их в routes.rb, но если коротко, то это фильтры /latest, /top и т. д. Для списка с пользовательским фильтром вам нужно использовать параметр запроса, например:

/latest?fun_level=5

Используйте пользовательский фильтр. Вы можете проследить класс TopicQuery из list_controller.rb, чтобы понять, как он работает; например, он добавляет ваш пользовательский фильтр как поддерживаемый параметр в контроллер. Ощущение загадочности возникает потому, что этот контроллер и класс берут на себя множество задач, таких как пагинация и различные фильтры, которые вам пришлось бы настраивать вручную, если бы вы выбрали другой путь.

«Другой способ», который имеет смысл (не используя /search, заметьте), заключался бы в создании собственного выделенного контроллера для маршрута, который использует класс TopicQuery так же, как это делает list_controller.rb. В любом случае, если вы добавляете совершенно новый маршрут, вам нужно будет создать контроллер Rails, так что это ещё один правдоподобный подход, хотя вам и придётся самостоятельно обрабатывать такие вещи, как пагинация. Если вы используете этот подход, всё равно следует применять пользовательский фильтр.

Я понимаю, что часть этого может показаться непрозрачной, однако вы имеете дело со сложной функциональностью. Чтобы объяснить всё подробно, мне пришлось бы написать курс из 10 частей. Что я, возможно, сделаю в ближайшее время :slight_smile:

ОБНОВЛЕНИЕ: Я думаю, что мне удалось (в основном) заставить это работать (!). Теперь это будет показывать «последние» темы, соответствующие значению пользовательского поля. (Метод #latest был ближайшим, который я смог найти и который имел смысл в файле config/routes.rb).

Важно, чтобы на страницу действительно загружались все темы, имеющие соответствующее значение пользовательского поля 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 (файл JavaScript, который активируется при переходе на соответствующую страницу. Так что этот JavaScript может находиться в инициализаторе или в коннекторе, связанном с выходным портом плагина. Мне нравится использовать код, идущий в комплекте с коннектором, поэтому я буду использовать здесь компонент setup):

const ajax = require('discourse/lib/ajax')

export default {
    setupComponent(args, component) {
      let parsedResultArray = []
      var endPoint = '/latest?fun_level=' + funLevel  //funLevel = переменная со значением из параметров
      ajax(endPoint).then(function (result) {
            console.log('результат списка тем для тем, соответствующих этому уровню веселья = ')
            console.log(result.topic_list.topics)
            //парсим результаты и загружаем их в parsedResultArray
            component.set('showTopics', parsedResultArray        
      })
   }
}

Теперь темы будут загружаться в {{topic-list topics=showTopics}}, который я использую в соответствующем компоненте, размещённом в шаблоне через my-plugin-outlet.

Это большой шаг вперёд. Огромное спасибо, @angus.

С помощью инструкций, приведенных выше (спасибо @angus и @JQ331), мне удалось успешно получить темы, указав значение для пользовательского поля темы, перейдя по адресу https://domain.com/latest?custom_field=custom_field_value.

Однако на этой странице, если я нажму на логотип сайта (или на кнопку Последнее в верхней панели), параметр запроса (custom_field) удаляется из URL, но темы остаются отфильтрованными по custom_field.

При обновлении страницы всё работает как ожидается (то есть отображаются все последние темы).

Как исправить это поведение?