Skip to content

Instantly share code, notes, and snippets.

@rondy
Created October 18, 2011 01:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rondy/1294418 to your computer and use it in GitHub Desktop.
Save rondy/1294418 to your computer and use it in GitHub Desktop.
Scoped filters
class BooksController < ApplicationController
def index
# params = { filters: { genre: "Westerns", sold_out: 1 } }
@books = Book.filtered(params[:filters])
end
end
class Book < ActiveRecord::Base
scope :filter_dependencies, joins(:author, :library, :publisher)
scope :genre, lambda { |genre| where(genre: genre) }
scope :sold_out, where(sold_out: true)
has_filter :with => :filter_dependencies do
field :genre
field :sold_out, boolean: true
end
end
# Inspired by thoughtbot approach
# http://robots.thoughtbot.com/post/11111383374/separate-your-filters
module HasFilter
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
def scoped_filter
@scoped_filter ||= ScopedFilter.new(self.scoped)
end
def has_filter(options={}, &block)
scoped_filter.scoped = send(options[:with]) if options[:with]
scoped_filter.instance_exec(&block) if block_given?
end
def filtered(restrictions={})
scoped_filter.filtered(restrictions)
end
end
class ScopedFilter
attr_reader :filters
attr_accessor :scoped
def initialize(scoped, &block)
@scoped = scoped
@restrictions = {}
@filters ||= Hash.new { |hash, key| hash[key] = [] }
end
def field(name, options={})
group = options[:boolean] ? :booleans : :attributes
filter = {}
filter[:field] = name
filter[:scope] = options[:to] || name
setup_filter group, filter
end
def filtered(restrictions={})
@restrictions = restrictions.reject_blank
@filtered = @scoped
apply_attributes_filter!
apply_booleans_filter!
@filtered
end
private
def setup_filter(group, args)
filters[group].push args
end
def apply_attributes_filter!
filters[:attributes].each { |filter| where(filter.merge(value: @restrictions[filter[:field]])) }
end
def apply_booleans_filter!
filters[:booleans].each { |filter| where(filter) if Boolean(@restrictions[filter[:field]]) }
end
def where(filter={})
scope = filter[:scope]
value = filter[:value]
has_restriction = @restrictions.has_key? filter[:field]
@filtered = @filtered.send(*[scope, value].compact) if has_restriction
end
end
end
ActiveRecord::Base.send :include, HasFilter
# OUTDATED
require "spec_helper"
describe ScopeFilter do
before :all do
BookFilter = Class.new(ScopeFilter) do
field :genre
field :sold_out, boolean: true
end
end
after :all do
Object.send :remove_const, :BookFilter
end
describe "BookFilter", "class methods", focus: true do
let :book_filter do
BookFilter
end
subject do
book_filter
end
its :superclass do
should eq ScopeFilter
end
describe "filters_for" do
subject do
book_filter.filters_for
end
it { subject[:attributes].should include :genre }
it { subject[:booleans].should include :sold_out }
end
end
describe "BookFilter", "instance methods" do
let :book_filter do
BookFilter.new(model, restrictions)
end
let :model do
mock(:model)
end
subject do
book_filter
end
describe "#filtered" do
context "attributes filter" do
let :restrictions do
{ genre: "Western" }
end
it do
model.should_receive(:genre).with("Western")
subject.filtered
end
end
context "booleans filter" do
let :restrictions do
{ sold_out: 1 }
end
it do
model.should_receive(:sold_out)
subject.filtered
end
end
context "blank filter" do
let :restrictions do
{ blank: "" }
end
it do
model.should_not_receive(:blank)
subject.filtered
end
end
context "unknown filter" do
let :restrictions do
{ unknown: "unknown" }
end
it do
model.should_not_receive(:unknown)
subject.filtered
end
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment