Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Filterable
# 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 the same name as the keys in <tt>filtering_params</tt>
# with their associated values. 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(key, value) if value.present?
end
results
end
end
end

Thanks this is a really useful pattern. Bookmarked!

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.

maebeale commented Oct 2, 2015

thanks @justinweiss! this is awesome!!! hope all is well w you.

can this be included in the base class? which will then inherit ?

krokrob commented Feb 16, 2016

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.

asory commented Mar 27, 2016

work in rails 4 ??
sorry how used in view?
i really want used this is simple and useful

gusridd commented Apr 11, 2016

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 :) 👍

guru28 commented Aug 19, 2016

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

Superpencil commented Apr 4, 2017 edited

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment