Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
# 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
This paste will be private.
From the Design Piracy series on my blog: