Report abuse

# 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