Пользовательские данные из формы GJS composer отсутствуют в `opts` для события `:topic_created`

Это продолжение обсуждения: Custom topic fields per category or custom topic entry form per category?

Я решил создать плагин. Сам компонент формы корректно рендерится через api.renderInOutlet. Однако данные не сохраняются на бэкенде в пользовательской таблице.

Настройка фронтенда:

  1. Я использую api.renderInOutlet("composer-fields", MyOutletConnectorComponent) для отображения компонента GJS (MyOutletConnectorComponent) при создании новой темы в определенной категории.

  2. Этот MyOutletConnectorComponent затем рендерит мой основной компонент формы GJS (MyMarketFormComponent).

  3. Внутри MyMarketFormComponent ввод пользователя обновляет JavaScript-объект, который затем устанавливается как свойство верхнего уровня в composerModel, предоставленном аргументами выхода. Например:

    // Действие внутри MyMarketFormComponent
    const currentData = this.args.composerModel.get("market_listing_data") || {};
    const updatedData = { ...currentData, [fieldKey]: newValue };
    this.args.composerModel.set("market_listing_data", updatedData); 
    
  4. В инициализаторе моего плагина (tecenc-market-composer.js) я использую api.serializeOnCreate("market_listing_data", "market_listing_data"), чтобы попытаться передать эти данные на бэкенд. Также я использую api.serializeToDraft("market_listing_data", "market_listing_data").

Проблема на бэкенде:

В моем файле plugin.rb, внутри обработчика DiscourseEvent.on(:topic_created) do |topic, opts, user| ... end:

  • Я проверяю, находится ли тема в правильной категории (это работает).
  • Затем я пытаюсь получить доступ к сериализованным данным, используя market_data = opts[:market_listing_data] (или opts["market_listing_data"]).
  • Проблема в том, что opts[:market_listing_data] постоянно равен nil.
  • Логирование opts.keys подтверждает, что мой ключ market_listing_data отсутствует на верхнем уровне хэша opts. Сам хэш opts содержит стандартные ключи, такие как :raw, :title, :category, :archetype и т.д.

Фрагмент моего файла plugin.rb для события:

# plugin.rb
# ...
module ::TecencMarket
  SERIALIZED_DATA_KEY = "market_listing_data".freeze
end
# ...
after_initialize do
  # ...
  DiscourseEvent.on(:topic_created) do |topic, opts, user|
    # ... (логика проверки категории) ...

    # Здесь market_data равен nil
    market_data = opts[TecencMarket::SERIALIZED_DATA_KEY.to_sym] 

    Rails.logger.info "[MyPlugin] Ключи Opts: #{opts.keys.inspect}"
    Rails.logger.info "[MyPlugin] market_data из opts: #{market_data.inspect}"

    if market_data.present? && market_data.is_a?(Hash)
      # ... обработка и сохранение данных в пользовательскую таблицу ...
    else
      Rails.logger.warn "[MyPlugin] Данные о рынке не найдены в opts, как ожидалось."
    end
  end
  # ...
end

Вопросы:

  1. Какой правильный и наиболее надежный способ гарантировать, что JavaScript-объект, установленный как свойство верхнего уровня в composerModel (например, composerModel.set("my_plugin_data_key", { ... });) с использованием компонентов GJS, будет корректно сериализован через api.serializeOnCreate и станет доступным в хэше opts события :topic_created на бэкенде?

  2. Ожидается ли, что api.serializeOnCreate("my_key", "my_key") будет работать для произвольных ключей верхнего уровня, установленных в composerModel из контекста GJS, или сериализация через custom_fields (например, установка composerModel.custom_fields.my_plugin_key = {...} и использование api.serializeOnCreate("custom_fields.my_plugin_key")) является единственным надежным/рекомендуемым подходом для такой передачи данных при создании новых тем?

  3. Существуют ли какие-либо специфические соображения или распространенные ошибки при использовании api.serializeOnCreate с данными, управляемыми компонентами GJS, которые могут привести к тому, что данные не будут включены в хэш opts?

Я подтвердил, что форма на фронтенде корректно собирает данные и устанавливает их в свойство composerModel.market_listing_data. Проблема, по-видимому, заключается исключительно в том, чтобы эти данные были сериализованы и переданы событию на бэкенде.

Любые рекомендации или примеры рекомендуемого паттерна для этого в современном Discourse будут очень кстати!

Спасибо!

Снова здравствуйте,

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

Текущий подход:

  1. Инициализатор на стороне фронтенда:
    • Я пытаюсь обеспечить, чтобы composerModel.custom_fields и composerModel.custom_fields["tecenc_market_data"] (ключ данных моего плагина) инициализировались как объекты. Моя последняя попытка включает использование api.modifyClass("model:composer", ...) для добавления логики инициализации в init() и clearState():

      // В инициализаторе моего плагина (tecenc-market-composer.js)
      api.modifyClass("model:composer", {
        pluginId: "tecencMarketInitCustomFieldsOnComposer",
        init() {
          this._super(...arguments); 
          if (!this.custom_fields || typeof this.custom_fields !== 'object') {
            this.custom_fields = {};
          }
          if (!this.custom_fields["tecenc_market_data"] || typeof this.custom_fields["tecenc_market_data"] !== 'object') {
            this.custom_fields["tecenc_market_data"] = {};
          }
        },
        clearState() {
          this._super(...arguments); 
          if (!this.custom_fields || typeof this.custom_fields !== 'object') {
            this.set("custom_fields", {});
          }
          let cf = this.get("custom_fields"); 
          if (!cf["tecenc_market_data"] || typeof cf["tecenc_market_data"] !== 'object') {
            const newCustomFields = { ...cf }; 
            newCustomFields["tecenc_market_data"] = {};
            this.set("custom_fields", newCustomFields);
          }
        }
      });
      
    • Далее я использую api.serializeOnCreate('custom_fields.tecenc_market_data'); и api.serializeToDraft('custom_fields.tecenc_market_data');.

    • Мой компонент формы GJS обновляет composerModel.custom_fields.tecenc_market_data.my_field = value; (путем установки нового объекта custom_fields для обеспечения реактивности).

  2. Бэкенд plugin.rb:
    • Вызывается Topic.register_custom_field_type("tecenc_market_data", :json).
    • Обработчик события :topic_created ожидает чтение данных из topic.custom_fields["tecenc_market_data"].

Новая блокирующая ошибка:

Несмотря на эти попытки инициализировать custom_fields в composerModel, я постоянно сталкиваюсь со следующей ошибкой JavaScript при попытке открыть композер для новой темы (нажав кнопку «Создать тему»):


Uncaught (in promise) Error: Property set failed: object in path "custom_fields" could not be found.

Ember 3

open composer.js:1010

open composer.js:1009

_setModel composer.js:1451

open composer.js:1401

openNewTopic composer.js:1410

createTopic list.js:167

clickCreateTopicButton d-navigation.gjs:224

_triggerAction d-button.gjs:138

Ember 11

_triggerAction d-button.gjs:135

click d-button.gjs:93

source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94366

Ember 2

source chunk.8d6366d70b85d1b69fdc.d41d8cd9.js:94040

Ember 26 property_set-BapAkp3X.js:79:10

Эта ошибка указывает на то, что this.model.custom_fieldscomposerModel) равен undefined или null, когда ядро Discourse в composer.js:1010 (внутри метода open, вызываемого openNewTopic) пытается установить вложенное свойство. Это происходит до того, как форма моего плагина успеет даже отрендериться.

Вопросы:

  1. Учитывая, что базовая модель Composer должна инициализировать custom_fields: {}, почему composerModel.custom_fields все еще может быть неопределенным в composer.js:1010 во время последовательности openNewTopic в этой версии Discourse?
  2. Является ли подход api.modifyClass("model:composer", ...) в инициализаторе правильным/наиболее эффективным способом гарантировать, что custom_fields (и вложенные ключи плагинов внутри него) инициализируются как объекты достаточно рано, чтобы предотвратить эту базовую ошибку? Если нет, какой паттерн рекомендуется?
  3. Возможно ли, что проблема связана с таймингом, когда шаги инициализации базового композера выполняются и ожидают custom_fields до того, как инициализаторы плагинов, использующие api.modifyClass, полностью применятся к прототипу или экземплярам модели Composer?

Любые insights по поводу ошибки «Property set failed» в ядре композера и лучших практик для безопасного взаимодействия плагинов с composerModel.custom_fields во время инициализации были бы крайне полезны.

Спасибо за ваше время!

Думаю, вам нужно добавить свои данные в сериализатор на стороне Rails.

Поступают ли ваши данные в Rails? Где вы их храните?

Посмотрите плагины, которые используют addToSerializer.

Я сохраняю данные в пользовательской таблице базы данных.

addToSerializer предназначен для передачи данных с сервера на клиент, верно?

Моя проблема заключается в передаче данных с клиента на сервер, чтобы я мог их сохранить. Поэтому я использую serializeOnCreate. Но проблема в том, что данные не попадают в хэш opts. Таким образом, на данный момент ответ: нет, пользовательские данные не попадают в хэш opts, как ожидалось. Они не поступают в Rails.

Я изучил этот обучающий плагин, но не смог заставить его работать для 6 пользовательских полей.

Удалось заставить это работать.

Я упустил этот шаг: api.serializeToTopic(MY_BLOB_KEY, \topic.${MY_BLOB_KEY}\);