Ask A Question

Notifications

You’re not receiving notifications from this thread.

Searchkick Aggregation for has_many_through association

Lubomir Herko asked in Gems / Libraries

Hey guys,

I have an ecommerce product catalogue, where I store products, product options and option values in this kind of fashion:

# models/product.rb
has_many :option_values
has_many :option_types, through: :option_values

# models/option_value.rb
belongs_to :option_type
belongs_to :product

#models/option_type.rb
has_many :option_values
has_many :products

I came up with this structure so I can have for example a lamp Product that can be connected to OptionType titled "Power supply included" via value OptionValue set to true. Data in database for this product are saved as follows:

#products
id: 22
title: "A lamp"

#option_values
id: 1
option_type_id: 5
product_id: 22
value: true

#option_types
id: 5
title: "Power supply included"

Every ProductCategory has its own specific set of option values, that's why I came up with this structure, it is inspired by Spotify architecture and I will need this in search/aggregates/filters, what leads me to my question:

How can i create aggregate search so I can select all products, that have "Power supply included" set to true? Do I need nested aggregates, or do I setup search_data in some fashion so I can access them via aggs?

Reply

I have been playing happy wheels. This is my favorite game ever..

Reply

Hi, I am solving very similar problem. And I think I crack it =)
For your product model you need add to search_data this

# product.rb
def search_data
  {
    option_value_id: option_values.map(&:id),
    option_type_option_value_id: option_values.map{ |option_value| option_value.option_type.id.to_s + ";" + option_value.id.to_s }
  }
end

option_type_option_value_id its a little hacky way that will help us to aggregate.
We take option_type id (like 5) and combine it with option_value id (like 10) and delimiter ; so we get something like this '5;10'
Elasticsearch will aggregate that string.

In your controller where you want to display catalog add to method

option_value_ids = array_of_option_value_ids_from_params

search_params = {}
search_params = search_params.merge(option_value_id: {all: option_value_ids}) if option_value_ids.present?

# Here we aggregate option_type_option_value_id and search with sended option values, I have pagination too
@products = Product.search "*", aggs: { option_type_option_value_id: {} }, where: search_params, page: params[:page], per_page: 25

# Here we are creating an object with option_type_id, option_value_id and count, we use this in our view
@aggs_products = @products.aggs["option_type_option_value_id"]["buckets"].map { |bucket| { option_type_id: bucket["key"].split(';')[0].to_i, option_value_id: bucket["key"].split(';')[1].to_i, count: bucket["doc_count"] } }

Now last piece the view.
Here we iterate all option types and control if we have it in our @aggs_products hash
If is there we then iterate over option type option values to test if the value is in our @aggs_products hash
If value is there we print title and count for this option (you could make it as link_to a send it as parameter to our method from above)

<% OptionType.all.each do |option_type| %>
<% if @aggs_products.any? { |h| h[:option_type_id] == option_type.id } %>
  <div class="option-type">
    <h3 class="option-type__title"><%= option_type.title %></h3>
    <ul>
      <% option_type.option_values.each do |option_value| %>
      <% if @aggs_products.any? { |h| h[:option_value_id] == option_value.id } %>
      <li>
        <%= option_value.title %> <span class="option-type__option-count">(<%= @aggs_products.detect {|c| c[:option_value_id] == option_value.id }[:count] %>)</span>
      </li>
      <% end %>
      <% end %>
    </ul>
  </div>
<% end %>
<% end %>
Reply
Join the discussion
Create an account Log in

Want to stay up-to-date with Ruby on Rails?

Join 78,890+ developers who get early access to new tutorials, screencasts, articles, and more.

    We care about the protection of your data. Read our Privacy Policy.

    Screencast tutorials to help you learn Ruby on Rails, Javascript, Hotwire, Turbo, Stimulus.js, PostgreSQL, MySQL, Ubuntu, and more.

    © 2023 GoRails, LLC. All rights reserved.