class PostSearchResultSet
  attr_reader :site

  def initialize(groonga_posts:, site:, query:, page:)
    @groonga_posts = groonga_posts
    @site = site
    @query = query
    @page = page

    @snippet = nil
    @posts = nil
  end

  def keywords
    @query.keywords
  end

  def posts
    @posts ||= @site.posts.includes(credits: [:participant]).published.where(id: searched_post_ids).order_by_recently.page(@page).per(50)
  end

  def snippet(text, html_options = {})
    (@snippet ||= create_snippet(html_options)).execute(text)
  end

  private

  def searched_post_ids
    @groonga_posts.map(&:_key)
  end

  def create_snippet(html_options)
    open_tag = "<span class='#{html_options[:class]}'>"
    close_tag = "</span>"
    options = {
      normalize: true,
      width: 200,
      html_escape: true,
    }
    @groonga_posts.expression.snippet([open_tag, close_tag], options)
  end
end

class PostSearchResultSet
  attr_reader :site

  def initialize(groonga_posts:, site:, query:, page:)
    @groonga_posts = groonga_posts
    @site = site
    @query = query
    @page = page

    @snippet = nil
    @posts = nil
  end

  def keywords
    @query.keywords
  end

  def posts
    @posts ||= @site.posts.includes(:category, credits: [:participant]).published.where(id: searched_post_ids).order_by_recently.page(@page).per(50)
  end

  def snippet(text, html_options = {})
    (@snippet ||= create_snippet(html_options)).execute(text)
  end

  private

  def searched_post_ids
    @groonga_posts.map(&:_key)
  end

  def create_snippet(html_options)
    open_tag = "<span class='#{html_options[:class]}'>"
    close_tag = "</span>"
    options = {
      normalize: true,
      width: 200,
      html_escape: true,
    }
    @groonga_posts.expression.snippet([open_tag, close_tag], options)
  end
end

Include category to fix N+1 problem for search result set
Include category to fix N+1 problem for search result set

The following queries are run:

    Category Load (0.1ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [
    ["id", 2]]
      Rendered posts/_summary.html.haml (11.7ms)
      Category Load (0.1ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [
    ["id", 1]]
      Rendered posts/_summary.html.haml (3.9ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (2.9ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (3.1ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (2.7ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (2.5ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (2.4ms)
      CACHE (0.0ms)  SELECT  "categories".* FROM "categories" WHERE "categories"."id" = $1 LIMIT 1  [["id", 1
    ]]
      Rendered posts/_summary.html.haml (2.5ms)
    ...
