Using scopes for search queries

105 views Asked by At

I'm working on improving my search functionality in my app by using models.

This is what I've been using so far:

searchController#index:

def index
    # Enable display of results page on direct access with no params
    return false if params[:s].nil?

    # Venues near search location using Lon/Lat and Geocoder
    @venues = Venue.near eval(params[:s]["geo"])

    # Search variables
    near_by_venues = @venues.to_a.map(&:id)
    s_activity = params[:s]["activity"].to_i
    s_start_time = (params[:s]["start_time"]).to_datetime
    s_end_time = (params[:s]["end_time"]).to_datetime

    # Filter availabilities based on search criteria
    @search = Availability.includes{facility.activities}
            .where{
              (facility.venue_id.in near_by_venues)
            }
            .where{
              (activities.id == s_activity) &
              (booking_id == nil) &
              (start_time >= s_start_time ) &
              (end_time <= s_end_time )
            }
            .order{start_time.desc}
            .references(:all)
end

This has worked well for me so far but I started thinking other use cases for search, like optional parameters or default parameters if for some reason a location wasn't provided or start/end time wasn't specified. This lead me to articles regarding scoped search where everything is handled at the model level instead of the controller.

I created these scopes in my Availability model:

class Availability < ActiveRecord::Base
  # Associations
  belongs_to :facility
  belongs_to :booking

  #  Scopes for search filters
  scope :close_to, -> (locations) { where{facility.venue_id.in locations} }
  scope :activity,  -> (activity) {where {activities.id == activity}}
  scope :start_date, -> (datetime) {where{created_at >= datetime}}
  scope :end_date, -> (datetime) {where{created_at <= datetime}}
  scope :not_booked, -> {where(booking: nil)}
  scope :order, -> {order{start_time.desc}}
end

I'm having trouble putting it all together. I'm not sure how to include the joins I have includes{facility.activities} in the model so when I do something like Availability.activity(5).not_booked it returns correctly.

2

There are 2 answers

6
K M Rakibul Islam On BEST ANSWER

You can include the activity within availability in the following fashion:

scope :activity, -> (activity) { includes({facility: :activities}).where{activities.id == activity}.references(:all) } 
1
Mark Swardstrom On

There are lots of ways to do this, but to keep it simple, I put these things in a concern. For example AvailabilitySearch, in app/models/concerns

In Availability, you include it

class Availability < ActiveRecord::Base

  include AvailabilitySearch

end

Then, in here, you can do whatever you need.

module AvailabilitySearch

  extend ActiveSupport::Concern

  module ClassMethods

    def search(params)
      results = self.includes(:relation_that_is_always_referenced)

      # use scopes or not
      results = results.where(:key => params[:key]).includes(:relation_that_is_sometimes_referenced) if params[:key]
      results = results.activity(params[:activity) if params[:activity]
      ...

      # and then return your results
      return results
    end
  end
end

The last thing, add some javascript to the search page to get rid of all the inputs that are blank from the url, so it's clean and makes sense if it's shared.

In coffeescript, given the search form has an id of 'search_form'

$(document).on 'ready page:load', ->
  $('#search_form').submit ->
    $(this).find(':input').filter(->
      !@value
    ).prop 'disabled', true
    true

  # If they hit back
  $('#search_form').find(':input').prop 'disabled', false

  # If they stop and click on the form again
  $('#search_form').on 'click', ->
    $(this).find(':input').prop 'disabled', false

  return