diff --git a/README.rdoc b/README.rdoc
index 6b4c359..6697038 100644
--- a/README.rdoc
+++ b/README.rdoc
@@ -5,7 +5,7 @@ GroupedScope aims to make two things easier in your ActiveRecord models. First,
easy way to group objects, second, to allow the group to share associated object via existing
has_many relationships. See installation and usage for more details.
-By the way, this plugin has been tested with rails 1.2.6 and 2.1.1.
+By the way, this plugin has been tested with rails 1.2.6, 2.0.4, and 2.1.1.
=== Installation & Usage
diff --git a/test/grouped_scope/has_many_association_test.rb b/test/grouped_scope/has_many_association_test.rb
index 09fe390..fd9689f 100644
--- a/test/grouped_scope/has_many_association_test.rb
+++ b/test/grouped_scope/has_many_association_test.rb
@@ -67,8 +67,7 @@ def setup
end
should 'use assoc extension SQL along with group reflection' do
- sql_regex = /SELECT \* FROM "?reports"? *WHERE \("?reports"?.employee_id IN \(2,3\) AND \("?reports"?."?title"? = 'URGENT'\)\)/
- assert_sql(sql_regex) do
+ assert_sql(select_from_reports, where_for_groups, where_for_urgent_title) do
@e2.group.reports.urgent
end
end
@@ -76,7 +75,7 @@ def setup
end
context 'training named scopes' do
-
+
setup do
@e1 = Factory(:employee_with_urgent_reports, :group_id => 1)
@e2 = Factory(:employee, :group_id => 1)
@@ -95,8 +94,8 @@ def setup
end
should 'use named scope SQL along with group reflection' do
- assert_sql(/body LIKE '%URGENT%'/,/"title" = 'URGENT'/,/employee_id IN/) do
- @e2.group.reports(true).with_urgent_title.with_urgent_body.inspect
+ assert_sql(select_from_reports, where_for_groups, where_for_urgent_body, where_for_urgent_title) do
+ @e2.group.reports.with_urgent_title.with_urgent_body.inspect
end
end
@@ -125,4 +124,23 @@ def setup
end
+ protected
+
+ def select_from_reports
+ /SELECT \* FROM "?reports"?/
+ end
+
+ def where_for_groups
+ /WHERE.*"?reports"?.employee_id IN \(2,3\)/
+ end
+
+ def where_for_urgent_body
+ /WHERE.*body LIKE '%URGENT%'/
+ end
+
+ def where_for_urgent_title
+ /WHERE.*"?reports"?."?title"? = 'URGENT'/
+ end
+
+
end
diff --git a/test/lib/boot.rb b/test/lib/boot.rb
index 2bf8410..3967d3d 100644
--- a/test/lib/boot.rb
+++ b/test/lib/boot.rb
@@ -26,8 +26,8 @@
require 'active_support'
unless defined? ActiveRecord::NamedScope
+ require 'core_ext'
require 'named_scope'
- require 'named_scope_patch'
+ require ActiveRecord::Base.respond_to?(:find_first) ? 'named_scope_patch_1.2.6' : 'named_scope_patch_2.0'
ActiveRecord::Base.send :include, GroupedScope::NamedScope
end
-
diff --git a/test/lib/core_ext.rb b/test/lib/core_ext.rb
new file mode 100644
index 0000000..8b64ba7
--- /dev/null
+++ b/test/lib/core_ext.rb
@@ -0,0 +1,20 @@
+
+unless Hash.instance_methods.include? 'except'
+
+ Hash.class_eval do
+
+ # Returns a new hash without the given keys.
+ def except(*keys)
+ rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
+ reject { |key,| rejected.include?(key) }
+ end
+
+ # Replaces the hash without only the given keys.
+ def except!(*keys)
+ replace(except(*keys))
+ end
+
+ end
+
+end
+
diff --git a/test/lib/named_scope.rb b/test/lib/named_scope.rb
index d7265eb..ccb3460 100644
--- a/test/lib/named_scope.rb
+++ b/test/lib/named_scope.rb
@@ -1,27 +1,20 @@
-## 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)
+ # * 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:
+ def scopes
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
end
@@ -46,7 +39,7 @@ def scopes #:nodoc:
# 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
+ # All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
# has_many associations. If,
#
# class Person < ActiveRecord::Base
@@ -76,7 +69,20 @@ def scopes #:nodoc:
# end
# end
#
+ #
+ # For testing complex named scopes, you can examine the scoping options using the
+ # proxy_options method on the proxy itself.
+ #
+ # class Shirt < ActiveRecord::Base
+ # named_scope :colored, lambda { |color|
+ # { :conditions => { :color => color } }
+ # }
+ # end
+ #
+ # expected_options = { :conditions => { :colored => 'red' } }
+ # assert_equal expected_options, Shirt.colored('red').proxy_options
def named_scope(name, options = {}, &block)
+ name = name.to_sym
scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options
when Hash
@@ -93,9 +99,15 @@ def named_scope(name, options = {}, &block)
end
end
- class Scope #:nodoc:
+ class Scope
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)/ }
+
+ [].methods.each do |m|
+ unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|^find$|count|sum|average|maximum|minimum|paginate|first|last|empty\?|respond_to\?)/
+ delegate m, :to => :proxy_found
+ end
+ end
+
delegate :scopes, :with_scope, :to => :proxy_scope
def initialize(proxy_scope, options, &block)
@@ -108,6 +120,30 @@ def reload
load_found; self
end
+ def first(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.first(*args)
+ else
+ find(:first, *args)
+ end
+ end
+
+ def last(*args)
+ if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
+ proxy_found.last(*args)
+ else
+ find(:last, *args)
+ end
+ end
+
+ def empty?
+ @found ? @found.empty? : count.zero?
+ end
+
+ def respond_to?(method, include_private = false)
+ super || @proxy_scope.respond_to?(method, include_private)
+ end
+
protected
def proxy_found
@found || load_found
diff --git a/test/lib/named_scope_patch.rb b/test/lib/named_scope_patch.rb
deleted file mode 100644
index a438834..0000000
--- a/test/lib/named_scope_patch.rb
+++ /dev/null
@@ -1,41 +0,0 @@
-## 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
-
-unless Hash.instance_methods.include? 'except'
- Hash.class_eval do
- # Returns a new hash without the given keys.
- def except(*keys)
- rejected = Set.new(respond_to?(:convert_key) ? keys.map { |key| convert_key(key) } : keys)
- reject { |key,| rejected.include?(key) }
- end
-
- # Replaces the hash without only the given keys.
- def except!(*keys)
- replace(except(*keys))
- end
- end
-end
diff --git a/test/lib/named_scope_patch_1.2.6.rb b/test/lib/named_scope_patch_1.2.6.rb
new file mode 100644
index 0000000..7f2a84a
--- /dev/null
+++ b/test/lib/named_scope_patch_1.2.6.rb
@@ -0,0 +1,61 @@
+
+ActiveRecord::Associations::AssociationProxy.class_eval do
+ protected
+ def with_scope(*args, &block)
+ @reflection.klass.send :with_scope, *args, &block
+ end
+end
+
+ActiveRecord::Associations::HasManyAssociation.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
+ create_scoping = {}
+ set_belongs_to_association_for(create_scoping)
+
+ @reflection.klass.with_scope(
+ :create => create_scoping,
+ :find => {
+ :conditions => @finder_sql,
+ :joins => @join_sql,
+ :readonly => false
+ }
+ ) do
+ @reflection.klass.send(method, *args, &block)
+ end
+ end
+ end
+end
+
+ActiveRecord::Associations::HasManyThroughAssociation.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(construct_scope) { @reflection.klass.send(method, *args, &block) }
+ end
+ end
+end
+
+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
+
diff --git a/test/lib/named_scope_patch_2.0.rb b/test/lib/named_scope_patch_2.0.rb
new file mode 100644
index 0000000..9a6250b
--- /dev/null
+++ b/test/lib/named_scope_patch_2.0.rb
@@ -0,0 +1,32 @@
+
+ActiveRecord::Associations::AssociationProxy.class_eval do
+ protected
+ def with_scope(*args, &block)
+ @reflection.klass.send :with_scope, *args, &block
+ end
+end
+
+
+ActiveRecord::Associations::AssociationCollection.class_eval do
+ protected
+ def method_missing(method, *args)
+ if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
+ if block_given?
+ super { |*block_args| yield(*block_args) }
+ else
+ super
+ end
+ elsif @reflection.klass.scopes.include?(method)
+ @reflection.klass.scopes[method].call(self, *args)
+ else
+ with_scope(construct_scope) do
+ if block_given?
+ @reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
+ else
+ @reflection.klass.send(method, *args)
+ end
+ end
+ end
+ end
+end
+