From 693b17eb517de86b7ccbea2d1d09bd229ba79b28 Mon Sep 17 00:00:00 2001 From: Ken Collins Date: Mon, 29 Sep 2008 11:10:10 -0400 Subject: [PATCH] Test 1.2.6, 2.0.4 and 2.1.1 named scope usage. New named scope back port method since mislav back port from will paginate was not complete for 1.2.6. --- README.rdoc | 2 +- .../has_many_association_test.rb | 28 +++++++-- test/lib/boot.rb | 4 +- test/lib/core_ext.rb | 20 ++++++ test/lib/named_scope.rb | 60 ++++++++++++++---- test/lib/named_scope_patch.rb | 41 ------------- test/lib/named_scope_patch_1.2.6.rb | 61 +++++++++++++++++++ test/lib/named_scope_patch_2.0.rb | 32 ++++++++++ 8 files changed, 187 insertions(+), 61 deletions(-) create mode 100644 test/lib/core_ext.rb delete mode 100644 test/lib/named_scope_patch.rb create mode 100644 test/lib/named_scope_patch_1.2.6.rb create mode 100644 test/lib/named_scope_patch_2.0.rb 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 +