# Half-baked alternative to has_finder:
# http://www.pivotalblabs.com/articles/2007/09/02/hasfinder-its-now-easier-than-ever-to-create-complex-re-usable-sql-queries
#
# This implementation is simpler, as it doesn't involve the use of
# AssociationProxies, but that also means that if you don't provide a kicker
# method (such as find or count), you end up with a Filter object rather than
# a usable resultset.
#
# This also does not implement dynamic scope conditions (which are likely
# necessary in your app; such an implementation would also also involve
# lambda functions).
#
# Exercise for the reader:
# Provide a singleton method to define filtration methods (remove boilerplate).
# Proxy object that handle underlying scope composition.
class Filter
def initialize(name, obj, scope)
@name = name
@obj = obj
@scope = scope
end
def method_missing(symbol, *args)
retval = nil
# with_scope becomes protected in 2.0 and we're not in an AR::Base subclass
@obj.send(:with_scope, @scope) do
# call the requested method nested in a with_scope block, allowing pass-thru as necessary
logger.debug "Filter '#{@name}' filter executing #{symbol} on #{@obj.name}"
retval = @obj.send(symbol, *args)
end
@obj.clear_filters!
retval
end
def clear_filters!
# delegate clear_filters! to the underlying object (will pass through if that happens to be a Filter)
@obj.send(:clear_filters!)
end
def with_scope(scope, &block)
# delegate with_scope to the underlying object (will pass through if that happens to be a Filter)
@obj.send(:with_scope, scope, &block)
end
def name
self.class.name
end
end
# Sample AR model
class Sample < ActiveRecord::Base
# Methods to support the filter chain
# get the deepest filter (or this object if none is active)
def self.filter
@filter || self
end
# Clear active filters after each method call
def self.clear_filters!
@filter = nil
end
# Filters
# To chain filters:
# Sample.green.public.count
# Sample.orange.public.find(:all)
def self.green
scope = {:find => {:conditions => ["color = ?", "green"]}}
if block_given?
with_scope(scope) do
yield self
end
else
@filter = Filter.new("green", filter, scope)
end
end
def self.orange
scope = {:find => {:conditions => ["color = ?", "orange"]}}
if block_given?
with_scope(scope) do
yield self
end
else
@filter = Filter.new("orange", filter, scope)
end
end
def self.public
scope = {:find => {:conditions => ["public = ?", true]}}
if block_given?
with_scope(scope) do
yield self
end
else
@filter = Filter.new("public", filter, scope)
end
end
end