カスタムフィールドに基づいてトピックを取得しますか?

@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 メソッドを実装する必要があるからです。

もっと直接的な方法があるのではないかと考えています。

「いいね!」 1

カスタムフィールドに基づいてトピックを取得する方法の一つを見つけました。検索して結果を取得するという方法です。

例えば、どこかに「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

これを行う方法は以下の通りです

  1. クライアント側で: api.addDiscoveryQueryParam を使用してトピックのクエリパラメータを追加します

  2. サーバー側で: 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
「いいね!」 3

編集:api.addDiscoveryQueryParam をさらに調べてみたところ、基本的な考え方はわかってきたと思います:

カスタムフィールド fun_level = super-duper-fun を持つすべてのトピックをプログラムで取得したいと考えています。コントローラーのメソッドで実現できるかもしれません(まだ検討中)。

別の方法として、ajax("/search") を使ってカスタムフィールド fun_level=super-duper-fun に基づいてすべてのトピックを検索する方法もあります。ただし、カスタムフィールドを作成するだけではこれを有効にするには不十分です。カスタムフィールド fun_level を、カテゴリやタグなどと同様に検索対象として使用できるように設定する必要があります。これは自動的に実行されるものではありません。

何らかの形で、js ファイル内の api.addDiscoveryQueryParamplugin.rb 内の TopicQuery を組み合わせる必要があります。しかし、正直なところ、まだうまく動作させることができていません。これらのメソッドを使用しているプラグインはいくつか見たのですが、それらがどのように「成果を収めている」のかを理解できませんでした。追加のコードが必要だとは思うのですが、まだ見つけていません。

これらのメソッドから、実際にカスタムフィールドを検索語として利用可能にするには、どのように進めればよいのでしょうか?

以前の返信

@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」を検索サイドバーのオプションとして追加する方法も知りたいです。私自身もその機能を追加したいと考えているからです。もしかすると、カスタムフィールドに基づいてトピックを読み込む最良の方法は、カスタムフィールドを検索オプションに追加し、クエリを fun_level: super-duper-fun として ajax("/search") を呼び出すことかもしれません。

したがって、検索関連の機能もここで重要になる可能性があります。しかし、現在の主なタスクは、ユーザーがそのページにアクセスした際に、カスタムフィールドに基づいてトピックをページに読み込むことです。

このトピック一覧ページは、Discourse に既存のトピック一覧ページと似た雰囲気を持たせるべきでしょうか、それとも本質的に異なるものにするべきでしょうか?

「いいね!」 2

本質的には異なりますが、ここでは、カスタムフィールド値(例:fun_level = ‘super-duper-fun’)を持つトピックを、ページに挿入している {{topic-list topics=selectedTopics showPosters=true}} コンポーネントにどのように読み込むかという点に焦点を当てています。

トピックリストを扱う際の問題は、既存の Discourse の発見(discovery)構造を拡張するのか、それとも独自のものを作成するのかによって、実装が変わるかどうかです。あなたが目指していることに関連する多くの質問は、ここでは割愛しています。

したがって、発見構造を拡張しない場合、上記で提案したことの最初の部分 は行いません。ただし、2 番目の部分、すなわち TopicQuery にカスタムフィルターを追加する作業は引き続き行ってください。また、list_controller.rb にマッピングされたエンドポイントに対して AJAX 呼び出しを行うクライアントサイドのルートも必要になります。config/routes.rblist# を検索することで、リストコントローラーのルートを確認できます。

Discourse の発見機能で使用しているのと同じトピックリストエンドポイントを使用すべきです。そうすれば、ページネーション(トピックリストコンポーネント内のスクロール時読み込みで処理されます)、権限処理、そして多くの他の機能が最初から利用可能になります。

つまり、以下のものが必要になります。

  1. カスタムフィルターを含む plugin.rb
  2. クライアントサイドのルートファイル
  3. クライアントサイドのテンプレート
「いいね!」 1

情報をありがとうございます。

カスタムフィールドの値に一致するトピックを取得するだけであれば、最も簡単な方法で問題ありません。現在、/fun_levels/:fun_level に動作するルート/パス/テンプレートを用意しており、そこで {{topic-list}} コンポーネントを読み込んでいます。もしその方法が ajax(/search) を用いてカスタムフィールドの値を検索するものであれば、それも問題ありません。むしろ、それが最も直接的な方法だと考えています。ただ、まだ実装できていないだけです。

念のため補足すると、現在の私の手法は以下の通りです。

  1. ajax でトピックを取得する(適切なエンドポイントが何か、またそれをどう設定するかを見極める必要があります。ここが鍵です)。
  2. 結果をパースする。
  3. component.set('showTopics', parsed-result) を実行して、トピックを {{topic-list topics=showTopics}} に読み込む。

この方法は少し不可解に感じられます。list_controller には def topics_by のようなメソッドが見えますが、それらのメソッドのいずれかをどのように変更すれば、カスタムフィールドの値に基づいてトピックを返せるようになるのでしょうか?

複数の理由から、これはお勧めしません。なぜか神秘めいたことを言うようですが、今すぐ完全に説明するには、私にはもう少し時間が必要です。

最も簡単な方法は、リストコントローラーに既存のエンドポイントのいずれかを使用することです。これらはすでにトピックのリストを提供するように設定されています。これらは routes.rb で見つけることができますが、簡単に言えば、それらは /latest/top などのフィルターです。カスタムフィルター付きのリストの場合は、以下のようなクエリパラメータを使用します。

\n/latest?fun_level=5\n

カスタムフィルターを使用することです。list_controller.rb から TopicQuery クラスを追跡して、その仕組みを確認できます。例えば、カスタムフィルターをコントローラーでサポートされるパラメータとして追加します。それが神秘めいているように感じるのは、そのコントローラーとクラスがページネーションやさまざまなフィルターなど、あなたが別の方法で行う場合は手動で設定する必要がある多くのことを処理しているからです。

「別の方法」(/search を使用しない方法)は、ルート用の独自の専用コントローラーを設定することです。これは list_controller.rb が行うように TopicQuery クラスを使用します。いずれにせよ完全に新しいルートを追加する場合は Rails のコントローラーを作成する必要がありますが、これはもう一つの実現可能なアプローチです。ただし、ページネーションなどの処理は自分で行う必要があります。このアプローチを使用する場合でも、カスタムフィルターを使用すべきです。

これらの一部は不明瞭に見えるかもしれませんが、ここでは複雑な機能を取り扱っています。これを完全に説明するには、10 部構成のコースを書く必要があります。実際、そうするかもしれません :slight_smile:

「いいね!」 2

更新:ほぼ動作するようになりました(!)。これで、カスタムフィールドの値に一致する「最新」のトピックが表示されます(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(関連ページにアクセスした際に有効化される 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('その fun レベルに一致するトピックのリスト結果 = ')
            console.log(result.topic_list.topics)
            //結果を解析し、parsedResultArray に読み込む
            component.set('showTopics', parsedResultArray        
      })
   }
}

これで、my-plugin-outlet 経由でテンプレートに配置された対応するコンポーネント内の {{topic-list topics=showTopics}} にトピックが読み込まれるようになります。

これは大きな前進です。どうもありがとうございました@angus

「いいね!」 4

上記の指示(@angusと@JQ331のおかげ)により、https://domain.com/latest?custom_field=custom_field_valueにアクセスすることで、カスタムトピックフィールドに値が設定されている場合にトピックを取得することができました。

しかし、そのページからサイトのロゴをクリックする(またはトップバーの最新ボタンをクリックする)と、URLからクエリパラメータ(custom_field)は削除されますが、トピックはカスタムフィールドでフィルタリングされたままになります。

リフレッシュすると、ページは期待どおりに動作します(つまり、すべての最新トピックが表示されます)。

この動作を修正するにはどうすればよいですか?

「いいね!」 1