【问题标题】:Nested searching in SunSpot SolrSunSpot Solr 中的嵌套搜索
【发布时间】:2023-07-03 18:20:01
【问题描述】:

我正在尝试实现基于 Solr 的消息线程搜索。每条消息可以有许多回复(回复只能是一个级别的深度。)。我想检索内容匹配搜索键或回复匹配搜索键的父消息。

例如:

Hello Jack
  Hello Janice
  How are you?
  ..

I am Janice
  How are you?

Welcome to the Jungle
  Nothing better to do.

搜索Janice 应返回以下结果集:

Hello Jack # one of the child messages matches the key word
I am Janice # parent message matched the keyword)

我的模型如下:

class Message < ActiveRecord::Base    
  belongs_to :parent, :class_name => "Message"
  has_many   :replies, :class_name => "Message", :foreign_key => :parent_id      
  # content      
  searchable do
    text :content
    integer :parent_id
  end     
end

用于指定嵌套子查询条件的 DSL 语法是什么?

编辑 1

我考虑创建一个复合文本索引字段来保存所有索引。但这种方法在我的场景中不可行,因为我必须确保回复符合某些附加条件。

class Message < ActiveRecord::Base    
  belongs_to :parent, :class_name => "Message"
  has_many   :replies, :class_name => "Message", :foreign_key => :parent_id      
  belongs_to :category
  # content      
  searchable do
    text :content
    integer :category_id
    integer :parent_id
  end     
end

在上述模型中,我想将文本搜索限制为给定类别。

【问题讨论】:

    标签: ruby-on-rails solr sunspot


    【解决方案1】:

    完成您要查找的内容的最佳方法是将回复的内容(以及您希望使其可搜索的任何其他字段)非规范化到其父消息中。

    这在 Sunspot 中非常简单。您可能会在线研究的另一个常见场景是根据其 cmets 的内容搜索博客文章。

    这里要注意一件重要的事情:由于非规范化,您需要一个 after_save 挂钩,以便回复可以在添加或更新时重新索引其父项。

    在您的情况下,更改可能看起来像这样......

    class Message < ActiveRecord::Base    
      # …
    
      after_save :reindex_parent
    
      searchable do
        # …
        text :replies_content
      end
    
      def replies_content
        replies.collect(&:content).join(" ")
      end
    
      def reindex_parent
        parent.solr_index!
      end
    
    end
    

    (如果您想保存几行而不是定义新方法,text :replies_content 也可以接受内联 lambda。这取决于您。)

    这种方法对搜索语法没有真正的改变,因为回复的所有内容都会集中到您的默认关键字搜索中。

    如果您有更具体的用例,您需要澄清您的问题,但这对我来说似乎是最好和最简单的方法。

    最后一点:例如,如果您的消息有很多回复,这种方法可能会有点繁重。确保使用 DelayedJob 或 Resque 异步索引可能是个好主意。但这是一个不同的对话。

    更新 1:使用某个 category_id 确定范围

    首先,我假设每个回复可能有一个与其父级不同的category_id。而且,重申一下,您希望针对父 回复文本内容执行关键字匹配,并且您希望按类别进行范围。

    我看到你有几个选择。我将从最简单的开始,然后描述一些可能的组合。最简单的方法是做一个非常基本的搜索——不用担心非规范化或任何其他问题——然后用 ActiveRecord 关联重建你的父子消息。

    @search = Message.search do
      keywords params[:q]
      with(:category_id, params[:category_id])
    end
    @messages = @search.results
    

    如您所见,category_id 的范围在 Sunspot 中非常简单。可能这是你的大部分问题,我刚刚离开并让它变得比它必须的更复杂:)

    从那里开始,其中一些@messages 将成为父母,一些将成为回复。确定哪个是哪个并相应地渲染当然在您的视图能力范围内。

    <% if message.parent %>
      …
    

    这里还有一些其他方法,具体取决于您的需求的确切性质。以上可能已经足够好了,所以我不会在这里详细说明。但是,如果您继续追求非规范化,您还可以为所有邮件回复的category_ids 包含一个多值整数列。类似integer :reply_category_ids, :multi =&gt; true

    后一种方法会为整个消息线程提供更松散的匹配,这可能值得也可能不值得进行非规范化的复杂性,具体取决于您的应用程序。我将把语法留给你,它主要来自我之前的示例。

    如您所见,这里有一些排列方式,具体取决于您要针对该类别的范围的时间和地点。希望我上面的示例足以让您继续了解您的应用的确切细节。

    【讨论】:

    • 尼克,我更新了我的问题以更好地解释我的要求。我曾考虑过这种方法,但它不能满足我的过滤要求。
    • 已更新以添加某些类别范围。希望这会给您一些好的想法,以帮助您进行下一步。
    【解决方案2】:

    非常感谢 Nick,当我使用跨所有表的任意子字符串启用全局搜索时,您的提示帮助我解决了我的问题。就我而言,我必须使用 FK 来检索父记录的属性并使其在子表中可搜索:

    searchable do
      ...
      text :ip_address,  as: :ip_address_textp # nested searching
      ...
    end
    
    private
    
    def ip_address
      Address.find(address_id).ip # retrieve attribute from parent record with FK
    end
    

    【讨论】: