diff --git a/.gitignore b/.gitignore
index 4d3ab48..7cd3efe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
.DS_Store
test/debug.log
-autotest
\ No newline at end of file
+test/lib/shoulda*
+autotest
diff --git a/test/helper.rb b/test/helper.rb
index 5b67da6..dd7effe 100644
--- a/test/helper.rb
+++ b/test/helper.rb
@@ -1,9 +1,8 @@
+require File.join(File.dirname(__FILE__),'lib/boot') unless defined?(ActiveRecord)
require 'test/unit'
-require 'rubygems'
require 'shoulda'
require 'quietbacktrace'
require 'mocha'
-require File.join(File.dirname(__FILE__),'lib/boot') unless defined?(ActiveRecord)
require 'factory_girl'
require 'lib/test_case'
require 'grouped_scope'
diff --git a/test/lib/boot.rb b/test/lib/boot.rb
index 81a3fb0..4110b74 100644
--- a/test/lib/boot.rb
+++ b/test/lib/boot.rb
@@ -1,3 +1,4 @@
+require 'rubygems'
plugin_root = File.expand_path(File.join(File.dirname(__FILE__),'..'))
framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].detect { |p| File.directory? p }
@@ -21,6 +22,16 @@
end
end
-require 'activerecord'
+require 'active_record'
require 'active_support'
+def enable_named_scope
+ return if defined? ActiveRecord::NamedScope
+ require 'named_scope'
+ require 'named_scope_patch'
+ ActiveRecord::Base.class_eval do
+ include GroupedScope::NamedScope
+ end
+end
+
+enable_named_scope
diff --git a/test/lib/named_scope.rb b/test/lib/named_scope.rb
new file mode 100644
index 0000000..d7265eb
--- /dev/null
+++ b/test/lib/named_scope.rb
@@ -0,0 +1,132 @@
+## stolen from: http://dev.rubyonrails.org/browser/trunk/activerecord/lib/active_record/named_scope.rb?rev=9084
+
+module GroupedScope
+ # This is a feature backported from Rails 2.1 because of its usefullness not only with will_paginate,
+ # but in other aspects when managing complex conditions that you want to be reusable.
+ module NamedScope
+ # All subclasses of ActiveRecord::Base have two named_scopes:
+ # * all, which is similar to a find(:all) query, and
+ # * scoped, which allows for the creation of anonymous scopes, on the fly:
+ #
+ # Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)
+ #
+ # These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
+ # intermediate values (scopes) around as first-class objects is convenient.
+ def self.included(base)
+ base.class_eval do
+ extend ClassMethods
+ named_scope :all
+ named_scope :scoped, lambda { |scope| scope }
+ end
+ end
+
+ module ClassMethods
+ def scopes #:nodoc:
+ read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
+ end
+
+ # Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
+ # such as :conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'}
+ # named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
+ # end
+ #
+ # The above calls to named_scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
+ # in effect, represents the query Shirt.find(:all, :conditions => {:color => 'red'}).
+ #
+ # Unlike Shirt.find(...), however, the object returned by Shirt.red is not an Array; it resembles the association object
+ # constructed by a has_many declaration. For instance, you can invoke Shirt.red.find(:first), Shirt.red.count,
+ # Shirt.red.find(:all, :conditions => {:size => 'small'}). Also, just
+ # as with the association objects, name scopes acts like an Array, implementing Enumerable; Shirt.red.each(&block),
+ # Shirt.red.first, and Shirt.red.inject(memo, &block) all behave as if Shirt.red really were an Array.
+ #
+ # These named scopes are composable. For instance, Shirt.red.dry_clean_only will produce all shirts that are both red and dry clean only.
+ # Nested finds and calculations also work with these compositions: Shirt.red.dry_clean_only.count returns the number of garments
+ # for which these criteria obtain. Similarly with Shirt.red.dry_clean_only.average(:thread_count).
+ #
+ # All scopes are available as class methods on the ActiveRecord descendent upon which the scopes were defined. But they are also available to
+ # has_many associations. If,
+ #
+ # class Person < ActiveRecord::Base
+ # has_many :shirts
+ # end
+ #
+ # then elton.shirts.red.dry_clean_only will return all of Elton's red, dry clean
+ # only shirts.
+ #
+ # Named scopes can also be procedural.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # In this example, Shirt.colored('puce') finds all puce shirts.
+ #
+ # Named scopes can also have extensions, just as with has_many declarations:
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :red, :conditions => {:color => 'red'} do
+ # def dom_id
+ # 'red_shirts'
+ # end
+ # end
+ # end
+ #
+ def named_scope(name, options = {}, &block)
+ scopes[name] = lambda do |parent_scope, *args|
+ Scope.new(parent_scope, case options
+ when Hash
+ options
+ when Proc
+ options.call(*args)
+ end, &block)
+ end
+ (class << self; self end).instance_eval do
+ define_method name do |*args|
+ scopes[name].call(self, *args)
+ end
+ end
+ end
+ end
+
+ class Scope #:nodoc:
+ attr_reader :proxy_scope, :proxy_options
+ [].methods.each { |m| delegate m, :to => :proxy_found unless m =~ /(^__|^nil\?|^send|class|extend|find|count|sum|average|maximum|minimum|paginate)/ }
+ delegate :scopes, :with_scope, :to => :proxy_scope
+
+ def initialize(proxy_scope, options, &block)
+ [options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
+ extend Module.new(&block) if block_given?
+ @proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
+ end
+
+ def reload
+ load_found; self
+ end
+
+ protected
+ def proxy_found
+ @found || load_found
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ if scopes.include?(method)
+ scopes[method].call(self, *args)
+ else
+ with_scope :find => proxy_options do
+ proxy_scope.send(method, *args, &block)
+ end
+ end
+ end
+
+ def load_found
+ @found = find(:all)
+ end
+ end
+ end
+end
diff --git a/test/lib/named_scope_patch.rb b/test/lib/named_scope_patch.rb
new file mode 100644
index 0000000..4bcaa34
--- /dev/null
+++ b/test/lib/named_scope_patch.rb
@@ -0,0 +1,25 @@
+## based on http://dev.rubyonrails.org/changeset/9084
+
+ActiveRecord::Associations::AssociationProxy.class_eval do
+ protected
+ def with_scope(*args, &block)
+ @reflection.klass.send :with_scope, *args, &block
+ end
+end
+
+
+# Rails 1.2.6
+ActiveRecord::Associations::HasAndBelongsToManyAssociation.class_eval do
+ protected
+ def method_missing(method, *args, &block)
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
+ super
+ elsif @reflection.klass.scopes.include?(method)
+ @reflection.klass.scopes[method].call(self, *args)
+ else
+ @reflection.klass.with_scope(:find => { :conditions => @finder_sql, :joins => @join_sql, :readonly => false }) do
+ @reflection.klass.send(method, *args, &block)
+ end
+ end
+ end
+end if ActiveRecord::Base.respond_to? :find_first