Rubioli
Rubioli

Reputation: 680

Ruby on Rails - Filter records based on users selected options

In my application, I have a Book model. I have around 10000k book records in my database. Basically, the application works as the user can select options and get a list of books that matches their inserted credentials.

Each of these books has type, language & genres.

Each book can have several type, language & genres (like an array)

title: 'Have fun book' 
type: ['pdf', 'paper', 'website']
language: ['Egnlish', 'French', 'German', 'Spanish']
genres: ['comedy']

My BooksController:

def book_params
  params.require(:book).permit(type:[], language:[], genres:[])
end

The form where the user can insert their credentials and filter the books looks like the picture below:

Book filter form

Currently, this is what I'm doing in my BooksController to filter the books:

@book_filter = Book
                .where(type: @book.type)
                .where(language: @book.language)
                .where(genres: @book.genres)

This works kind of fine, but I have a few issues with it. If for example, the user doesn't select any Book type/ type or any other option, instead of getting all, I get nil and for that reason, no books are shown to the user.

What I have in mind is that if the option wasn't selected, either where for that options doesn't get affected or it passes all.

I tried this with no luck:

@book_filter = Book
                .where(type: @book.type || '')
                .where(language: @book.language || '')
                .where(genres: @book.genres || '')

My second issue is that I feel the filter could be written much smarter & Rails way.

Thanks in advance and any help is appreciated!

Rails 5.1

Upvotes: 2

Views: 2994

Answers (2)

Aaron Castro
Aaron Castro

Reputation: 399

Based on https://stackoverflow.com/a/54401841/3058437 but replacing public_send for where worked for me. Rails 5

module Filterable
  extend ActiveSupport::Concern

  module ClassMethods
    def filter(filtering_params)
      results = self.where(nil)
      filtering_params.each do |key, value|
        results = results.where("#{key} = ?", value) if value.present?
      end
      results
    end
  end
end

Upvotes: 0

Rubioli
Rubioli

Reputation: 680

In your models/concerns create a module:

module Filterable
  extend ActiveSupport::Concern

  module ClassMethods
    def filter(filtering_params)
      results = self.where(nil)
      filtering_params.each do |key, value|
        results = results.public_send(key, value) if value.present?
      end
      results
    end
  end
end

Include the module in your model as below:

class Product
  include Filterable
  ...
end

Then in your controller do:

@products = Product.filter(params.slice(:status, :location, :starts_with))

private

# A list of the param names that can be used for filtering the Product list
def filtering_params(params)
  params.slice(:status, :location, :starts_with)
end

The reason behind the module is because this code is reusable and can be used in any part of your application.

PS: Please keep in mind variables or models are not the same as the question but the solution is a general solution that can be implemented to any application.

Upvotes: 3

Related Questions