jhund / filterrific

Filterrific is a Rails Engine plugin that makes it easy to filter, search, and sort your ActiveRecord lists.
http://filterrific.clearcove.ca
MIT License
912 stars 125 forks source link

Filterrific Search & Sort Filters No Longer Working After Adding Checkbox Filters #98

Open ghost opened 8 years ago

ghost commented 8 years ago

I have a list of books. There are filters on top of the list for search and your standard sort dropdown. Then on the side are checkbox filters (see image below).

enter image description here



I setup the Filterrific gem according to the documentation, only for the top search and sort filters at first. They worked great on the "All Books" list page, "Series" book list page and the "Collections" book list page. These are all managed by their own controllers but all use the "title" model. A few days after getting the top filters to work, I got the side filters to work using Filterrific. I only did the side filters on the "All Books" list page for now. I wanted to get everything working on that one first before moving it to the other pages.

So the side filters work perfectly but now the search and sort filters no longer work as expected. If I change the sort order nothing happens. Let's say I change it to list the titles from Z to A. The list doesn't change. If I tick one of the side filters then the list changes according to the side filter. BUT if I untick the side filter then the list changes according to the sort dropdown option I'd chosen prior to ticking the side filter. Changing the sort filter again does nothing. It's only working after I tick and untick a side filter. The search filter doesn't work at all anymore.

My "Title" model:

  `class Title < ActiveRecord::Base

  # Tenant Of
  belongs_to :account, :inverse_of => :titles
  accepts_nested_attributes_for :account

  default_scope { where(account_id: Account.current_id) }

  belongs_to :writer, :inverse_of => :titles
  accepts_nested_attributes_for :writer

  mount_uploader :cover, CoverUploader
  mount_uploader :cover2, Cover2Uploader
  mount_uploader :cover3, Cover3Uploader

 def to_param
   "#{id}-#{title.parameterize}"
  end

  filterrific(
  default_filter_params: { sorted_by: 'published_desc' },
  available_filters: [
    :sorted_by,
    :search_query,
    :with_btype,
    :with_bformat,
    :with_age,
    :with_series,
    :with_collection,
    :with_guide
  ]
)

# default for will_paginate
  self.per_page = 10

# Search by Author or Title
  scope :search_query, lambda { |query|
  return nil  if query.blank?
  terms = query.downcase.split(/\s+/)
  terms = terms.map { |e|
    (e.gsub('*', '%') + '%').gsub(/%+/, '%')
  }
  num_or_conds = 2
  where(
    terms.map { |term|
      "(LOWER(titles.title) LIKE ? OR LOWER(titles.name) LIKE ?)"
    }.join(' AND '),
    *terms.map { |e| [e] * num_or_conds }.flatten
  )
}

# Sort By All These Options
  scope :sorted_by, lambda { |sort_option|
  # extract the sort direction from the param value.
  direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
  case sort_option.to_s
  when /^title_/
    order("LOWER(titles.title) #{ direction }")
  when /^price_/
    order("titles.price #{ direction }")
  when /^author_/
    order("LOWER(titles.name) #{ direction }")
  when /^published_/
    order("titles.published #{ direction }")
  else
    raise(ArgumentError, "Invalid sort option: #{ sort_option.inspect }")
  end
}

 def self.options_for_sorted_by
    [
      ['Title - A to Z', 'title_asc'],
      ['Title - Z to A', 'title_desc'],
      ['Price - Low to High', 'price_asc'],
      ['Price - High to Low', 'price_desc'],
      ['Author - A to Z', 'name_asc'],
      ['Author - Z to A', 'name_desc'],
      ['Newest to Oldest', 'published_desc'],
      ['Oldest to Newest', 'published_asc']
    ]
  end

#checkbox Side Filters

# Book Types
scope :with_btype, lambda { |flag|
  where(btype:[flag])
}

# Book Formats
scope :with_bformat, lambda { |flag|
  where(bformat:[flag])
}

# Age Groups
scope :with_age, lambda { |flag|
  where(age:[flag])
}

# Book Series
scope :with_series, lambda { |flag|
  where(sname:[flag])
}

# Book Collections
scope :with_collection, lambda { |flag|
  where(cname:[flag])
}

# Guides
scope :with_guide, lambda { |flag|
  where(gname:[flag])
}

end`

My "Books" Controller:

class BooksController < ApplicationController
  skip_before_filter :require_login
  skip_before_filter :not_authenticated
  before_action :set_title, only: [:show]
  layout "unify"

  def index
    @filterrific = initialize_filterrific(
      Title,
      params[:filterrific],
      select_options: {
        sorted_by: Title.options_for_sorted_by
      },
      :persistence_id => false,
    ) or return

    @titles = @filterrific.find.unscope(where: :account_id).paginate(:page => params[:page])

    respond_to do |format|
      format.html
      format.js

    end

  rescue ActiveRecord::RecordNotFound => e
    puts "Had to reset filterrific params: #{ e.message }"
    redirect_to(reset_filterrific_url(format: :html)) and return
  end

  def show
  end

  private

  def set_title
      @title = Title.unscope(where: :account_id).find(params[:id])
  end

  def title_params
      params.require(:title).permit(:cover, :cover2, :cover3, :title, :name, :btype, :genre, :price, :bformat, :pages, :edition, :age, :series, :sname, :collection, :cname, :guide, :gname, :movie, :mname, :isbn, :isbn13, :published, :description, :audio, :audio_html, :video, :video_html, :quote, :barns_noble, :amazon_us, :alibris, :abebooks, :books_million, :amazon_uk, :writer_id, :account_id, :active)
  end
end

My "Books Index" view:

<!--=== Breadcrumbs v4 ===-->
<div class="breadcrumbs-v4">
    <div class="container">
        <ul class="breadcrumb-v4-in">
            <li><a href="/">Home</a></li>
            <li class="active">All Books</li>
        </ul>
    </div><!--/end container-->
</div>
<!--=== End Breadcrumbs v4 ===-->

<!--=== Content Part ===-->
<div class="content container">
    <div class="row">
        <div class="col-md-3 filter-by-block md-margin-bottom-60">
            <h1>Filter By</h1>
            <div class="panel-group" id="accordion">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion" href="#collapseOne">
                                Type
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <%= form_for_filterrific @filterrific do |f| %>
                    <div id="collapseOne" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_btype, {multiple: true}, 'Childrens', nil) %>
                                        <i></i>
                                        Children's

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_btype, {multiple: true}, 'Young Adult', nil) %>
                                        <i></i>
                                        Young Adult

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_btype, {multiple: true}, 'Fiction', nil) %>
                                        <i></i>
                                        Fiction

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_btype, {multiple: true}, 'Non-Fiction', nil) %>
                                        <i></i>
                                        Non-Fiction

                                    </label>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

            <div class="panel-group" id="accordion-v3">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion-v3" href="#collapseThree">
                                Format
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_bformat, {multiple: true}, 'Hardcover', nil) %>
                                        <i></i>
                                        Hardcover

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_bformat, {multiple: true}, 'Paperback', nil) %>
                                        <i></i>
                                        Paperback

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_bformat, {multiple: true}, 'Audiobook CD', nil) %>
                                        <i></i>
                                        Audiobook CD

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_bformat, {multiple: true}, 'E-book', nil) %>
                                        <i></i>
                                        E-book

                                    </label>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

           <div class="panel-group" id="accordion-v3">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion-v3" href="#collapseThree">
                                Age
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_age, {multiple: true}, '0 - 3', nil) %>
                                        <i></i>
                                        0 - 3

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_age, {multiple: true}, '4 - 6', nil) %>
                                        <i></i>
                                        4 - 6

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_age, {multiple: true}, '7 - 9', nil) %>
                                        <i></i>
                                        7 - 9

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_age, {multiple: true}, '10 - 12', nil) %>
                                        <i></i>
                                        10 - 12

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_age, {multiple: true}, '13 And Up', nil) %>
                                        <i></i>
                                        13 And Up

                                    </label>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

            <div class="panel-group" id="accordion-v3">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion-v3" href="#collapseThree">
                                Series
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_series, {multiple: true}, 'The Mysterious Mrs. Bean', nil) %>
                                        <i></i>
                                        The Mysterious Mrs. Bean

                                    </label>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

            <div class="panel-group" id="accordion-v3">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion-v3" href="#collapseThree">
                                Collections
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_collection, {multiple: true}, 'Classic Noir', nil) %>
                                        <i></i>
                                        Classic Noir

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_collection, {multiple: true}, 'True Crime', nil) %>
                                        <i></i>
                                        True Crime

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_collection, {multiple: true}, 'Lesbian', nil) %>
                                        <i></i>
                                        Lesbian

                                    </label>
                                </li>
                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

            <div class="panel-group" id="accordion-v3">
                <div class="panel panel-default">
                    <div class="panel-heading">
                        <h2 class="panel-title">
                            <a data-toggle="collapse" data-parent="#accordion-v3" href="#collapseThree">
                                Guides
                                <i class="fa fa-angle-down"></i>
                            </a>
                        </h2>
                    </div>
                    <div id="collapseThree" class="panel-collapse collapse in">
                        <div class="panel-body">
                            <ul class="list-unstyled checkbox-list">
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_guide, {multiple: true}, 'Survival', nil) %>
                                        <i></i>
                                        Survival

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_guide, {multiple: true}, 'Travel', nil) %>
                                        <i></i>
                                        Travel

                                    </label>
                                </li>
                                <li>
                                    <label class="checkbox">
                                        <%= f.check_box(:with_guide, {multiple: true}, 'Sexual', nil) %>
                                        <i></i>
                                        Sexual

                                    </label>
                                </li>

                            </ul>
                        </div>
                    </div>
                </div>
            </div><!--/end panel group-->

            <%= link_to('RESET FILTERS', reset_filterrific_url, :class => 'btn-u btn-brd btn-brd-hover btn-u-lg btn-u-sea-shop btn-block') %>
        </div>

        <div class="col-md-9">
            <div class="row margin-bottom-5">
                <div class="col-sm-2 result-category">
                    <h3>BOOKS</h3>
                </div>
                <div class="col-sm-10">

                    <ul class="list-inline clear-both">
                        <li class="grid-list-icons">
                            <%= link_to('Reset Filters', reset_filterrific_url, :class => 'btn-u btn-u-sea-shop') %>
                        </li>

                        <li class="sort-list-btn">
                            <h3>Sort :</h3>
                            <div class="btn-group">
                                <%= f.select(:sorted_by, @filterrific.select_options[:sorted_by], class: 'btn btn-default dropdown-toggle') %>
                            </div>
                        </li>

                        <li class="sort-list-btn">
                            <h3>Search :</h3>
                            <div class="btn-group">
                                <%= f.text_field(:search_query, class: 'filterrific-periodically-observed') %>
                            </div>
                        </li>

                    </ul>
                      <%= render_filterrific_spinner %>
                      <% end %>
                </div>
            </div><!--/end result category-->

        <div id="filterrific_results" class="filter-results">
        <%= render( partial: 'books/list', locals: { titles: @titles } ) %>
        </div><!--/end filter resilts-->

        </div>
    </div><!--/end row-->
</div><!--/end container-->
<!--=== End Content Part ===-->

My "index.js":

<% js = escape_javascript(
  render(partial: 'books/list', locals: { titles: @titles })
) %>
$("#filterrific_results").html("<%= js %>");

My "list" partial:

               <% @titles.each do |title| %>
                <div class="list-product-description product-description-brd margin-bottom-30">
                    <div class="row">
                        <div class="col-sm-4">
                            <a href="shop-ui-inner.html"><%= image_tag(title.cover_url(:listview), class: "img-responsive sm-margin-bottom-20") if title.cover? %></a>

                        </div>
                        <div class="col-sm-8 product-description">
                            <div class="overflow-h margin-bottom-5">
                                <ul class="list-inline overflow-h">
                                    <li><h4 class="title-price"><%= link_to book_url(title) do %><%= title.title %><% end %></h4></li>
                                    <li><span class="gender text-uppercase">By <%= title.name %></span></li>
                                </ul>
                                <div class="margin-bottom-10">
                                    <span class="title-price margin-right-10"><%= title.btype %> | <%= title.bformat %> | <%= title.age %></span>
                                </div>
                                <div class="margin-bottom-10">
                                    <span class="title-price margin-right-10"><%= title.sname %> | <%= title.cname %> | <%= title.gname %></span>
                                </div>
                                <div class="margin-bottom-10">
                                    <span class="title-price margin-right-10">$<%= title.price %></span>
                                </div>
                                <p class="margin-bottom-20"><%= (raw title.description.truncate(500)) %></p>
                                <ul class="list-inline add-to-wishlist margin-bottom-20">

                                </ul>
                                <%= link_to('Read More ...', book_url(title), :class => 'btn-u btn-u-sea-shop') %>
                            </div>
                        </div>
                    </div>
                </div>
                <% end %> 

            <div class="text-center">
                <%= will_paginate @titles, renderer: BootstrapPagination::Rails %>
            </div><!--/end pagination-->

I was under the impression all of these different filters should work under one filterrific form. I have tried separating them but that didn't work either because I wasn't really sure how to have two separately working filterrific forms. I've tried putting the filters in the same order in the model as they are in the view. I've moved the filterrific_results div in and out of the partial. Still I have this strange behavior. It seems like with the way this is setup, all of the filters should be working accurately. I'm not sure what else to try.

I'm using: filterrific (2.0.5) rails (4.2.3) ruby-2.0.0-p643

Sorry, I posted this on Stackoverflow and there is no response and this is the only thing delaying this site launch. I figure if I post here too maybe other github users can help .

ghost commented 8 years ago

I'm removing the default scope from the whole app to see if this helps. After thinking more about it, I don't really need this to be a multi-tenant app since only one user is adding books in the backend.

Update: Still doesn't work. :(

ghost commented 8 years ago

After changing to this format : http://www.lauradhamilton.com/how-to-filter-by-multiple-options-filterrific-rails

Which does give all the needed elements for the side filters , I've abandoned trying to get this to work with the checkboxes. I'll have to come up with something else.

After making the change only one set of checkboxes would work (the first set). Upon putting things back like they were above, still only the first set of checkboxes would work and nothing else.

I removed the filterrific interaction with the checkboxes and left only the search and sort filter and now those are working.

matias-eduardo commented 7 years ago

I'm having a similar issue. Everything works well except the :with_type checkboxes. Am I missing anything?

Model

  filterrific(
    default_filter_params: { sorted_by: 'name_asc' },
    available_filters: [
      :sorted_by,
      :search_query,
      :with_type
    ]
  )

  scope :with_type, lambda { |card_types|
    where(type: [*card_types])
  }

Application Controller

  def initialize_filterrific_for_cards
    initialize_filterrific(
      Card,
      params[:filterrific],
      select_options: {
        sorted_by: Card.options_for_sorted_by
      },
      persistence_id: true,
      default_filter_params: { sorted_by: 'name_asc' }
    )
  end

DecksController - decks#build

  def build
    @filterrific = initialize_filterrific_for_cards or return
    @cards = @filterrific.find.page(params[:page])

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

  rescue ActiveRecord::RecordNotFound => e
    puts "Had to reset filterrific params: #{ e.message }"
    redirect_to(reset_filterrific_url(format: :html)) and return
  end

View

        <%= form_for_filterrific @filterrific do |f| %>
          <ul class="card_types_filter">
            <li>
              <%= f.check_box :with_type, {multiple: true}, "Monster", false %>
              <%= f.label :with_type, value: "Monster", class: "monster_label noselect" do %>
                <%= image_tag "card_icons/normal_card_icon.png" %>
                Monsters
              <% end %>
            </li>
            <li>
              <%= f.check_box :with_type, {multiple: true}, "Spell", false %>
              <%= f.label :with_type, value: "Spell", class: "spell_label noselect" do %>
                <%= image_tag "card_icons/spell_card_icon.png" %>
                Spells
              <% end %>
            </li>
            <li>
              <%= f.check_box :with_type, {multiple: true}, "Trap", false %>
              <%= f.label :with_type, value: "Trap", class: "trap_label noselect" do %>
                <%= image_tag "card_icons/trap_card_icon.png" %>
                Traps
              <% end %>
            </li>
          </ul>
       <% end %>
matias-eduardo commented 7 years ago

Solved my issue. Used the latest github version to allow multiple inputs. {multiple: true} was breaking it.

gem 'filterrific', git: 'https://github.com/ayaman/filterrific.git'
usamagoldev commented 7 years ago

i have tried this new GitHub version the multiple issue us solved but now my single input filter not working? have you tried this ?

matias-eduardo commented 7 years ago

What do you mean by single input filter? A single checkbox? I'll help you out if show me the code or the error you're getting in the logs.

afuno commented 7 years ago

All the above methods do not work. scope only works if a string is passed in the URL, but not an array.

For example, if you send something similar to this in the parameters:

with_categories_ids:
    - '24'
    - '33'
    - '45'

The eponymous scope in the Post model does not work. But if you change the passed parameter to a string:

with_categories_ids: '33'

Then all at once will work.

It is impossible for Rails to work normally with Filterrific. I do not understand why the author (@jhund) of Filterrific is so irresponsible about this issue.