-
Star
(278)
You must be signed in to star a gist -
Fork
(34)
You must be signed in to fork a gist
-
-
Save justinweiss/9065666 to your computer and use it in GitHub Desktop.
# Call scopes directly from your URL params: | |
# | |
# @products = Product.filter(params.slice(:status, :location, :starts_with)) | |
module Filterable | |
extend ActiveSupport::Concern | |
module ClassMethods | |
# Call the class methods with names based on the keys in <tt>filtering_params</tt> | |
# with their associated values. For example, "{ status: 'delayed' }" would call | |
# `filter_by_status('delayed')`. Most useful for calling named scopes from | |
# URL params. Make sure you don't pass stuff directly from the web without | |
# whitelisting only the params you care about first! | |
def filter(filtering_params) | |
results = self.where(nil) # create an anonymous scope | |
filtering_params.each do |key, value| | |
results = results.public_send("filter_by_#{key}", value) if value.present? | |
end | |
results | |
end | |
end | |
end |
This could be seriously dangerous if you don't whitelist the params in the controller:
params = {destroy: 1}
Product.filter(params)
@skateinmars If you look at the original blog. we only send that params that were sliced( filtered )
http://www.justinweiss.com/blog/2014/02/17/search-and-filter-rails-models-without-bloating-your-controller/
This is a pretty nice way of doing this sort of thing. @justinweiss, what would you do if you needed to do a bit of preprocessing on the params? For example, lets say I want to filter a Post model by Tags. My query string is /posts?tag_ids=10,11,12
Would you put a method in the scope? in the model? in a model concern? What if I need to do this same operation on several models? Interested in hearing your ideas! Thanks!
Why don't you make a gem 😄 so we can include in Gemfile ?
Thanks anyway !!!
I think present?
isn't the right method to chose. You can't do /hashtags?parent_id=nil
.
thanks @justinweiss! this is awesome!!! hope all is well w you.
can this be included in the base class? which will then inherit ?
Hi @justinweiss! And thanks for the gist 😄
I try it and something went wrong line 16, I can't call the method 'key' (named 'orientation') on my results
which is an array of instances of class Product
. I do not understand of does that could work.
work in rails 4 ??
sorry how used in view?
i really want used this is simple and useful
Thanks for your code it worked for me right from the gist. Nonetheless, I've created a scope with 'joins' so the results could appear more than once. In your opinion, Should I include a 'distinct' in that scope only, or should that behavior be included globally in the filter method as in https://gist.github.com/gusridd/9e80f763bae56a425aff310eeef0ae0f ?
That helped a lot. Thanks :) 👍
thanks but i have a doubt that how i can create scope for join table
Thank for this gist, I use it often, but with some extras :
cattr_accessor :filters
def available_filters(*filters)
self.filters ||= []
self.filters += scopes
self.filters.uniq!
end
And in method def filter(filtering_params)
:
filtering_params = filtering_params.symbolize_keys.slice(*self.filters)
I use symbolise_keys
to ensure it will match with the available_filters
.
The slice extract only the authorised params defined in models like this :
class Person < ActiveRecord::Base
[...]
available_scopes :name, :role, :age, :phone_number
[...]
end
And, I use also the role definition on available_filters like this (not handled in my examples) :
available_scopes :name, :role, :age, :phone_number, as: :admin
available_scopes :name, :role, as: :user
For Join table (many to many through)
class Item < ApplicationRecord
has_many :categorizations
has_many :categories, through: :categorizations
scope :category, ->(category) { Item.joins(:categories).where category: category }
scope :other, ->(other) { where other: other }
end
Thank you so much!
Thank you!
@justinweiss Just upgraded to Ruby 2.6.0 and started getting an ArgumentError wrong number of arguments (given 1, expected 0)
and using the example in your article, it would be this line in the controller @products = Product.filter(params.slice(:status, :location, :starts_with))
that generates the error.
If I go back to the step before you put this into the Filterable module, the error disappears.
filtering_params(params).each do |key, value|
@products = @products.public_send(key, value) if value.present?
end
def filtering_params(params)
params.slice(:status, :location, :starts_with)
end
More context from my app. Given this snippet, post Ruby 2.6.0
upgrade, yields the error where prior there was no issue.
def index
@companies = Company.includes(:agency).order(Company.sortable(params[:sort]))
@companies = @companies.filter(params.slice(:ferret, :geo, :status))
end
It seems that my first line creates the issue, so the question would be how to preserve my includes
and order
params on line 1 and keep this working as a module.
With this option it resumes working (includes and order as well as filtering) without me modifying line 1.
def index
@companies = Company.includes(:agency).order(Company.sortable(params[:sort]))
filtering_params(params).each do |key, value|
@companies = @companies.public_send(key, value) if value.present?
end
end
@justinweiss Been looking at this some more this morning. The following also appears to work. Preserves filtering, preserves includes and order. Doesn't generate Argument Error.
def index
@companies = Company.filter(params.slice(:ferret, :geo, :status))
@companies = @companies.includes(:agency).order(Company.sortable(params[:sort]))
end
Any insights?
Got some advice from @shuriu on this error.
ruby 2.6 added
#filter
method on enumerables that takes a block. So I’m guessing that’s why you’re getting this issue. More info:
In the first example the
filter
call was applied to an enumerable, which conflicts with the ruby method. In the second example, thefilter
call is applied to the Company class which doesn’t conflict with the ruby method, and that’s why it works.
My advice is to rename your filterable class method to something like
filter_from_params
so it’s: 1) expressing intent more clearly, 2) it reduces the possibility of clashing with other generic methods.
So, I renamed def filter(filtering_params)
to def filter_from_params(filtering_params)
and all is happy again. Leaving this here for anyone else who runs across this issue.
I ran into this issue also after upgrading to Ruby 2.6. I was able to get around this by renaming my filter
method to filter_by
, but it's surprising that the definition in the model does not take precedence over the Enumerable
s alias for select
.
My very own version:
without the explicit anonymous scope and redundant self
's
module Filterable
extend ActiveSupport::Concern
class_methods do
def filter(filter_attributes, params)
results = all;
filter_attributes.each do |attribute|
results = results.send(attribute, params[attribute]) if params[attribute].present?
end
results
end
end
end
Hi All,
I would like to know how can I implement this Filterable module in my views. I ma new to Rails..
I have table with 40 plus columns but I would like to apply filters for few columns like :status, :name etc
I should be able to dropdown, select the required status and apply filters
thanks inadvance for your support..
I had some issues using this with pundit gem policy scopes. I suggest renaming filter to filter_by. Has the benefit of consistent naming with scopes.
Thanks this is a really useful pattern. Bookmarked!