Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass default filter into Filterrific get

I finally got my filterrific get working and its a great gem, if not a little complex for a noob like me.

My original index page was filtering the active records based on those nearby to the user like this:

def index 

location_ids = Location.near([session[:latitude], session[:longitude]], 50, order: '').pluck(:id)
@vendor_locations = VendorLocation.includes(:location).where(location_id: location_ids)

@appointments = Appointment.includes(:vendor).
  where(vendor_id: @vendor_locations.select(:vendor_id))

end

So this pulls in all of the Appointments with Vendors in the area, but how do I pass this over to the Filterrific search:

@filterrific = initialize_filterrific(
  params[:filterrific],
  select_options:{ sorted_by: Appointment.options_for_sorted_by, with_service_id: Service.options_for_select },
  ) or return

@appointments = @filterrific.find.page(params[:page])

respond_to do |format|
  format.html
  format.js
end

It seems like the Filterrerrific is loading ALL of the appointments by default, but I want to limit to the ones nearby. What am I missing?

like image 707
Seth Merritt Avatar asked Dec 24 '16 06:12

Seth Merritt


1 Answers

What you appear to be missing is a param default_filter_params to filterrific macro in the model. (Your question didn't mention that you made any adjustments to the VendorLocation model, since that is the object that you want to filter, that's where the macro should be called. Maybe you just omitted it from your question...)

From the model docs:

filterrific(
    default_filter_params: { sorted_by: 'created_at_desc' },
    available_filters: [
      :sorted_by,
      :search_query,
      :with_country_id,
      :with_created_at_gte
    ]
  )

You probably found this already, it was on the first page of the documentation, but there's more important stuff in the example application that you need (I ran into this too, when I was just recently using Filterrific for the first time.)

The information on the start page is not enough to really get you started at all. You have to read a bit further to see the other ways you may need to change your models, model accesses, and views in order to support Filterrific.

The part that makes the default filter setting effective is this default_filter_params hash (NOT select_options, which provides the options for "select" aka dropdown boxes. That's not what you want at all, unless you're doing a dropdown filter.) This hash holds a list of the scopes that need to be applied by default (the hash keys) and the scope parameter is used as the hash value.

That default_filter_params hash may not be the only thing you are missing... You also must define those ActiveRecord scopes for each filter that you want to use in the model, and name these in available_filters as above to make them available to filterrific:

scope :with_created_at_gte, lambda { |ref_date|
  where('created_at >= ?', ref_date)
end

It's important that these scopes all take an argument (the value comes from the value of the filter field on the view page, you must add these to your view even if you want to keep them hidden from the user). It's also important that they always return ActiveRecord associations.

This is more like what you want:

scope :location_near, lambda { |location_string|
  l = Location.near(location_string).pluck(:id)
  where(location_id: l)
end

The problem with this approach is that in your case, there is no location_string or any single location variable, you have multiple coordinates for your location parameters. But you are not the first person to have this problem at all!

This issue describes almost exactly the problem you set out to solve. The author of Filterrific recommended embedding the location fields into hidden form fields in a nested fields_for, so that the form can still pass a single argument into the scope (as in with_distance_fields):

<%= f.fields_for :with_distance do |with_distance_fields| %>
  <%= with_distance_fields.hidden_field :lat, value: current_user.lat %>
  <%= with_distance_fields.hidden_field :lng, value: current_user.lng %>
  <%= with_distance_fields.select :distance_in_meters, 
    @filterrific.select_options[:with_distance] %>
<% end %>

... make that change in your view, and add a matching scope that looks something like (copied from the linked GitHub issue):

scope :with_distance,  -> (with_distance_attrs) {
  ['lng' => '-123', 'lat' => '49', 'distance_in_meters' => '2000']
  where(%{
    ST_DWithin(
      ST_GeographyFromText(
        'SRID=4326;POINT(' || courses.lng || ' ' || courses.lat || ')'
      ),
      ST_GeographyFromText('SRID=4326;POINT(%f %f)'),
      %d
    )
  } % [with_distance_attrs['lng'], with_distance_attrs['lat'], with_distance_attrs['distance_in_meters']])
}

So, your :with_distance scope should go onto the VendorLocation model and it should probably look like this:

scope :with_distance,  -> (with_distance_attrs) {
  lat = with_distance_attrs['lat']
  lng = with_distance_attrs['lng']
  dist = with_distance_attrs['distance']
  location_ids = Location.near([lat, lng], dist, order: '').pluck(:id)
  where(location_id: location_ids)
end

Last but not least, you probably noticed that I removed your call to includes(:location) — I know you put it there on purpose, and I didn't find it very clear in the documentation, but you can still get eager loading and have ActiveRecord optimize into a single query before passing off the filter work to Filterrific by defining your controller's index method in this way:

def index
  @appointments = Appointment.includes(:vendor).
    filterrific_find(@filterrific).page(params[:page])
end

Hope this helps!

like image 137
Kingdon Avatar answered Nov 09 '22 21:11

Kingdon