Ask A Question

Notifications

You’re not receiving notifications from this thread.

How do I filter records through other models

Owen asked in Rails

Desired result:

I want 2 things:

In my list of aggregation links I want to be able to add the aggregation of property characteristics (e.g., properties with a balcony), property type (e.g., villa, apartment, studio etc) and property condition (e.g., new, newly renovated, in construction etc.).
In my search form I want to have a select box / radio buttons for characteristics, property type and condition which (1) shows a collection of available items in the database and (2) when I select an item and it searches for all the properties with that characteristic / property type / condition.
Actual result:

When select a property type (like Villa) what I get are all the properties regardless of property type. The same for condition and characteristics.

With aggregation feature of the searchkick gem I'm also not able to show the aggregation of property type, condition or characteristics.

Within the documentation of searchkick they talk about it being able to search thru multiple models, but when I try that out in my controller what I get is the error:

enter image description here

Configuration

I have a rails app (v6.1.0) which has a Property (belongs to property_type, condition and has many characteristics thru characteristic_properties), PropertyType (has_many properties), Condition (has_many properties) and Characteristic (has_many properties thru characteristic_properties) model and I've implemented elastic search thru the searchkick gem.

Since then I've added with the help of seachkick aggregations of property attributes and I've added a Google map which shows where the properties are located on the map. And finally, I've added pagination with the Pagy gem. Taking this in consideration this is how #index looks in the properties_controller.rb

def index
args = {}
args[:surface_plot] = {}
args[:surface_plot][:gte] = params[:surface_plot_min] if params[:surface_plot_min].present?
args[:surface_plot][:lte] = params[:surface_plot_max] if params[:surface_plot_max].present?
args[:q_bathrooms] = {}
args[:q_bathrooms][:gte] = params[:q_bathrooms_min] if params[:q_bathrooms_min].present?
args[:q_bathrooms][:lte] = params[:q_bathrooms_max] if params[:q_bathrooms_max].present?
args[:q_bedrooms] = {}
args[:q_bedrooms][:gte] = params[:q_bedrooms_min] if params[:q_bedrooms_min].present?
args[:q_bedrooms][:lte] = params[:q_bedrooms_max] if params[:q_bedrooms_max].present?
args[:q_bedrooms] = params[:q_bedrooms] if params[:q_bedrooms].present?
args[:region] = params[:region] if params[:region].present?
args[:characteristics] = params[:characteristics] if params[:characteristics].present?
args[:buy] = params[:buy] if params[:buy].present?
args[:rent] = params[:rent] if params[:rent].present?
args[:share] = params[:share] if params[:share].present?
args[:floor] = {}
args[:floor][:gte] = params[:floor_min] if params[:floor_min].present?
args[:floor][:lte] = params[:floor_max] if params[:floor_max].present?
args[:floor] = params[:floor] if params[:floor].present?
args[:surface_interior] = {}
args[:surface_interior][:gte] = params[:surface_interior_min] if params[:surface_interior_min].present?
args[:surface_interior][:lte] = params[:surface_interior_max] if params[:surface_interior_max].present?
args[:sell_price] = {}
args[:sell_price][:gte] = params[:sell_price_from] if params[:sell_price_from].present?
args[:sell_price][:lte] = params[:sell_price_to] if params[:sell_price_to].present?
args[:sell_price][:gte] = params[:sell_price_min] if params[:sell_price_min].present?
args[:sell_price][:lte] = params[:sell_price_max] if params[:sell_price_max].present?
price_ranges = [{ to: 150000 }, {from: 150000, to: 300000}, {from: 300000, to: 500000}, {from: 500000, to: 1000000}, {from: 1000000}]

@properties = if params[:l]

          sw_lat, sw_lng, ne_lat, ne_lng = params[:l].split(",")

          Property.search("*", page: params[:page], per_page: 9, where: {
                              location: { top_left: {
                                  lat: ne_lat,
                                  lon: sw_lng
                                },
                                bottom_right: {
                                  lat: sw_lat,
                                  lon: ne_lng
                                }
                              }
                            },
                            aggs: {
                              region: {},
                              buy: {},
                              rent: {},
                              share: {},
                              sell_price: { ranges: price_ranges },
                              q_bedrooms: {},
                              surface_interior: {},
                              floor: {},
                              characteristics: {},
                              surface_plot: {},
                              q_bathrooms: {}
                            })
        elsif params[:near]
          # Property.near(params[:near]).page(params[:page]).per(9)

          location = Geocoder.search(params[:near]).first
          Property.search "*", page: params[:page], per_page: 9, where: {
            location: {
              near: {
                lat: location.latitude,
                lon: location.longitude
              }, within: "10mi"
            }
          },
          aggs: {
            region: {},
            buy: {},
            rent: {},
            share: {},
            sell_price: { ranges: price_ranges },
            q_bedrooms: {},
            surface_interior: {},
            floor: {},
            characteristics: {},
            surface_plot: {},
            q_bathrooms: {}
          }
        else
          # byebug
          @query = params[:q].presence || "*"
          @properties = Searchkick.search @query, page: params[:page], per_page: 9, models: [Property, PropertyType], where: args, aggs:  {
            region: {},
            buy: {},
            rent: {},
            share: {},
            sell_price: { ranges: price_ranges },
            q_bedrooms: {},
            surface_interior: {},
            floor: {},
            characteristics: {},
            surface_plot: {},
            q_bathrooms: {}
          }
        end

@pagy = Pagy.new_from_searchkick(@properties, link_extra: 'data-remote="true"')
end
And this is how property.rb looks like

class Property < ApplicationRecord
searchkick locations: [:location]
Pagy::SEARCHKICK
geocoded_by :address
after_validation :geocode, if: :address_changed?
has_many_attached :photos
belongs_to :property_type
belongs_to :condition
has_many :characteristic_properties
has_many :characteristics, through: :characteristic_properties
has_many :viewings, dependent: :destroy
has_many :users, through: :viewings
belongs_to :user

def address
[street, city, zip_code].compact.join(", ")
end

def address_changed?
street_changed? || city_changed? || zip_code_changed?
end

def search_data
attributes.merge location: { lat: latitude, lon: longitude }
end
end
This is my search form on the property results page:

<%= form_with url: properties_path, method: :get do |f| %>
<!-- Price -->



Price
<!-- Heroicon name: solid/chevron-down -->




<!--
Dropdown menu, show/hide based on menu state.

Entering: "transition ease-out duration-100"
From: "transform opacity-0 scale-95"
To: "transform opacity-100 scale-100"
Leaving: "transition ease-in duration-75"
From: "transform opacity-100 scale-100"
To: "transform opacity-0 scale-95"
-->





<%= f.label :sell_price_min, "Price Range", class: "mx-1" %>


<%= f.select :sell_price_min, [["No Min", ""], ["100mil€", "100000"], ["130mil€", "130000"], ["150mil€", "150000"], ["200mil€", "200000"], ["250mil€", "250000"], ["300mil€", "300000"], ["350mil€", "350000"], ["400mil€", "400000"], ["450mil€", "450000"], ["500mil€", "500000"], ["550mil€", "550000"], ["600mil€", "600000"], ["650mil€", "650000"], ["700mil€", "700000"], ["750mil€", "750000"], ["800mil€", "800000"], ["850mil€", "850000"], ["900mil€", "900000"], ["1M€", "1000000"], ["1.25M€", "1250000"], ["1.5M€", "1500000"], ["1.75M€", "1750000"], ["2M€", "2000000"], ["2.25M€", "2250000"], ["2.5M€", "2500000"], ["2.75M€", "2750000"], ["3M€", "3000000"], ["3.5M€", "3500000"], ["4M€", "4000000"], ["5M€", "5000000"], ["10M€", "10000000"], ["20M€", "20000000"]], {}, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>
<%= f.select :sell_price_max, [["No Max", ""], ["100mil€", "100000"], ["130mil€", "130000"], ["150mil€", "150000"], ["200mil€", "200000"], ["250mil€", "250000"], ["300mil€", "300000"], ["350mil€", "350000"], ["400mil€", "400000"], ["450mil€", "450000"], ["500mil€", "500000"], ["550mil€", "550000"], ["600mil€", "600000"], ["650mil€", "650000"], ["700mil€", "700000"], ["750mil€", "750000"], ["800mil€", "800000"], ["850mil€", "850000"], ["900mil€", "900000"], ["1M€", "1000000"], ["1.25M€", "1250000"], ["1.5M€", "1500000"], ["1.75M€", "1750000"], ["2M€", "2000000"], ["2.25M€", "2250000"], ["2.5M€", "2500000"], ["2.75M€", "2750000"], ["3M€", "3000000"], ["3.5M€", "3500000"], ["4M€", "4000000"], ["5M€", "5000000"], ["10M€", "10000000"], ["20M€", "20000000"]] , {}, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>




<!-- Bedrooms -->



Bedrooms
<!-- Heroicon name: solid/chevron-down -->








<%= f.label :q_bedrooms_min, "Bedrooms", class: "mx-1" %>


<%= f.select :q_bedrooms_min, [["All Beds", ""], ["1+", "1"], ["2+", "2"], ["3+", "3"], ["4+", "4"], ["5+", "5"]], {}, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>




Region
<%= f.label :region, "Region", class: "mx-1" %>
<%= f.select :region, @properties.aggs["region"]["buckets"].sort_by { |b| b["key"] }.collect { |bucket| bucket["key"] }, { include_blank: "All Regions", selected: "All Regions" }, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>
More
<div data-menu-target="toggleable" class="hidden z-10 origin-top-left absolute left-0 mt-2 max-w-sm w-96 py-4 px-2 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none" role="menu" aria-orientation="vertical" aria-labelledby="options-menu">
  <div class="py-1" role="none">
    <div class="flex flex-col">
      <div class="py-2">
        <div class="flex justify-center py-2">
        <!-- radio_button(object_name, method, tag_value, options = {}) public -->
        <!-- This example requires Tailwind CSS v2.0+ -->
          <div class="flex justify-center">
            <span class="relative z-0 inline-flex shadow-sm rounded-md">
              <% if params[:buy] == "true" %>
                <%= link_to "Buy", request.params.except(:buy), id: "type", class: "relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 text-sm font-medium focus:z-10 focus:outline-none focus:border-white focus:bg-blue-500 focus:text-white hover:no-underline text-white bg-blue-500 duration-300 ease-in-out" %>
              <% else %>
                <%= link_to "Buy", request.params.merge(buy: true), id: "type", class: "relative inline-flex items-center px-3 py-2 rounded-l-md border border-gray-300 text-sm font-medium focus:z-10 focus:outline-none focus:border-white focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out" %>
              <% end %>
              <% if params[:rent] == "true" %>
                <%= link_to "Rent", request.params.except(:rent), id: "type", class: "-ml-px relative inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium  focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 hover:no-underline text-white bg-blue-500 duration-300 ease-in-out" %>
              <% else %>
                <%= link_to "Rent", request.params.merge(rent: true), id: "type", class: "-ml-px relative inline-flex items-center px-3 py-2 border border-gray-300 text-sm font-medium  focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out" %>
              <% end %>
              <% if params[:share] == "true" %>
                <%= link_to "Share", request.params.except(:share), id: "type", class: "-ml-px relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 text-sm font-medium  focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 hover:no-underline text-white bg-blue-500 duration-300 ease-in-out" %>
               <% else %>
                <%= link_to "Share", request.params.merge(share: true), id: "type", class: "-ml-px relative inline-flex items-center px-3 py-2 rounded-r-md border border-gray-300 text-sm font-medium  focus:z-10 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out" %>
               <% end %>
            </span>
          </div>
        </div>
      </div>
      <div class="py-2">
        <div>
          <%= f.label :floor_min, "Floor", class: "mx-1" %>
        </div>
        <div class="flex">
          <%= f.select :floor_min, [["All Floors", ""], ["1st floor and up", "1"], ["2nd floor and up", "2"], ["3rd floor and up", "3"], ["4th floor and up", "4"], ["5th floor and up", "5"]], {}, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>
        </div>
      </div>
      <div class="py-2">
        <div>
          <%= f.label :surface_interior_min, "Interior Surface", class: "mx-1" %>
        </div>
        <div class="flex">
          <%= f.number_field :surface_interior_min, value: params[:surface_interior_min], placeholder: "Min", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))", class: "w-full rounded m-1" %>
          <span class="flex items-center"> - </span>
          <%= f.number_field :surface_interior_max, value: params[:surface_interior_max], placeholder: "Max", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))", class: "w-full rounded m-1" %>
        </div>
      </div>
      <div class="py-2">
        <div>
          <%= f.label :surface_plot_min, "Size Plot", class: "mx-1" %>
        </div>
        <div class="flex">
          <%= f.select :surface_plot_min, [["No Min", ""], ["150m²+", "150"], ["200m²+", "200"], ["250m²+", "250"], ["300m²+", "300"], ["350m²+", "350"], ["400m²+", "400"], ["450m²+", "450"], ["500m²+", "500"], ["1000m²+", "1000"], ["1500m²+", "1500"], ["2000m²+", "2000"], ["2500m²+", "2500"], ["3000m²+", "3000"], ["3500m²+", "3500"], ["4000m²+", "4000"], ["4500m²+", "4500"], ["5000m²+", "5000"], ["6000m²+", "6000"], ["7000m²+", "7000"], ["8000m²+", "8000"], ["9000m²+", "9000"], ["1ha+", "10000"], ["2ha+", "20000"]], {}, { class: "mx-1 mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))" } %>
        </div>
      </div>
      <div class="py-2">
        <div>
          <%= f.label :q_bathrooms_min, "Bathrooms", class: "mx-1" %>
        </div>
        <div class="flex justify-center py-2">
          <!-- radio_button(object_name, method, tag_value, options = {}) public -->
          <!-- This example requires Tailwind CSS v2.0+ -->
          <span class="relative z-0 inline-flex shadow-sm rounded-md">
            <button type="button" class="bathroom-btn relative inline-flex items-center px-4 py-2 rounded-l-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_1, "Any", class: "cursor-pointer" %>
            </button>
            <button type="button" class="bathroom-btn -ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "1", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_1, "1+", class: "cursor-pointer" %>
            </button>
            <button type="button" class="bathroom-btn -ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "2", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_2, "2+", class: "cursor-pointer" %>
            </button>

            <button type="button" class="bathroom-btn -ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "3", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_3, "3+", class: "cursor-pointer" %>
            </button>
            <button type="button" class="bathroom-btn -ml-px relative inline-flex items-center px-4 py-2 border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "4", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_4, "4+", class: "cursor-pointer" %>
            </button>
            <button type="button" class="bathroom-btn -ml-px relative inline-flex items-center px-4 py-2 rounded-r-md border border-gray-300 bg-white text-sm font-medium text-gray-700 hover:bg-gray-50 focus:z-10 focus:outline-none focus:ring-1 focus:ring-blue-500 focus:bg-blue-500 focus:text-white hover:no-underline hover:text-white hover:bg-blue-500 duration-300 ease-in-out">
              <%= f.radio_button :q_bathrooms_min, "5", onchange: "this.form.dispatchEvent(new Event('submit', {bubbles: true}))",class: "absolute transform scale-0" %>
              <%= f.label :q_bathrooms_min_5, "5+", class: "cursor-pointer" %>
            </button>
          </span>
        </div>
      </div>
    </div>
  </div>
</div>


<% end %>
I've been struggling with this for awhile now. I hope there's someone out there who can help me. I'd appreciate it a lot.

Reply
Reply
Join the discussion
Create an account Log in

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

Join 81,842+ 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.

    © 2024 GoRails, LLC. All rights reserved.