Qqwy
Qqwy

Reputation: 5629

Rails: multiselect filtering with a blank option

This is a problem I'm coming across regularly. Let me explain it by a simplified example:

Say I want to show a search page where results can be filtered by selecting one or multiple product categories. In the view, this looks like:

<%= select_tag("product_categories", options_from_collection_for_select(@product_categories, 'id', 'name'), multiple:true, include_blank:"(all categories)" %>

Now, in the controller, I have something like:

@filtered_products = Product.all

...

if params[:product_categories].present? 
   @filtered_products = @filtered_products.where(category_id: params[:product_categories].map(&:to_i))
end

...
#etc

However, as it is impossible to deselect a multiselect when it has clicked, there is a blank option. But, when this option is set, params[:product_categories] contains [""]. This results in the if-statement to evaluate, and as "".to_i == 0, we only get products with category 0 (which usually is none, as ActiveRecord starts ids from 1 in the database). This is not the desired result, as in fact we want all products when the blank option is selected.

Handling this case is made even more difficult because, it is possible to accidentally select both the blank option and one or multiple other options. So this case needs to be handled as well.

I have changed the if-statement to

if params[:product_categories].present? && params[:product_categories].any? && (params[:product_categories].length > 1 || params[:product_categories].first != "")
   ...
end

It works, but this code is very ugly. I am wondering if there is a nicer, more DRY, Rails-like way to do this.

Upvotes: 5

Views: 1048

Answers (3)

Mahesh
Mahesh

Reputation: 6426

Just improving Florin's answer a bit.

params[:product_categories].reject!(&:empty?).any?  if params[:product_categories].length > 1     

Upvotes: 4

akbarbin
akbarbin

Reputation: 5105

When you have no one categories selected you can add hidden_field same as product_categories to avoid [""] with nil value before your select options.

<%= hidden_field_tag "product_categories" %>
<%= select_tag("product_categories", options_from_collection_for_select(@product_categories, 'id', 'name'), multiple:true, include_blank:"(all categories)" %>

Then to handle it. It does not need to map(&:id) because "" will be generated into 0 automatically in query.

if params[:product_categories] != [""]
   @filtered_products = @filtered_products.where(category_id: params[:product_categories])
end

These is why embedded array always showing in multiple select options. I hope this make clean your code.

Upvotes: 5

Florin Ionita
Florin Ionita

Reputation: 271

Try

if params[:product_categories].reject!(&:empty?).any?

end

Upvotes: 4

Related Questions