Skip to content

Commit

Permalink
Use latest named scope plugin for cross platform tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
metaskills committed Oct 3, 2008
1 parent e3a0920 commit ca3c363
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 174 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@

*0.3* (October 2nd 2008)

* Add additional NamedScope patches for attribute_condition.
Also added GroupedScope::CoreExt to follow suite for GroupedScope::SelfGrouping attribute_conditions.


*0.2* (September 29th 2008)

* Add WillPaginate test and confirm grouped scope, named scope, and will paginate all play together. [Ken Collins]
Expand Down
8 changes: 1 addition & 7 deletions test/lib/boot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,5 @@
gem 'mislav-will_paginate', '2.3.4'
require 'will_paginate'
WillPaginate.enable_activerecord

unless defined? ActiveRecord::NamedScope
require 'core_ext'
require 'named_scope'
require ActiveRecord::Base.respond_to?(:find_first) ? 'named_scope_patch_1.2.6' : 'named_scope_patch_2.0'
ActiveRecord::Base.send :include, ActiveRecord::NamedScope
end
require 'named_scope'

173 changes: 6 additions & 167 deletions test/lib/named_scope.rb
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,168 +1,7 @@
module ActiveRecord
module NamedScope
# All subclasses of ActiveRecord::Base have two named_scopes:
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
# * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
#
# 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 :scoped, lambda { |scope| scope }
end
end

module ClassMethods
def scopes
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 <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
#
# 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 <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
#
# Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
# as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
# <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
#
# These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
#
# All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
# <tt>has_many</tt> associations. If,
#
# class Person < ActiveRecord::Base
# has_many :shirts
# end
#
# then <tt>elton.shirts.red.dry_clean_only</tt> 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, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
#
# Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
#
# class Shirt < ActiveRecord::Base
# named_scope :red, :conditions => {:color => 'red'} do
# def dom_id
# 'red_shirts'
# end
# end
# end
#
#
# For testing complex named scopes, you can examine the scoping options using the
# <tt>proxy_options</tt> 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
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
attr_reader :proxy_scope, :proxy_options

[].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)
[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

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
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
unless defined? ActiveRecord::NamedScope
require "named_scope/core_ext"
require "named_scope/named_scope"
require "named_scope/named_scope_patch_#{ActiveRecord::Base.respond_to?(:find_first) ? '1.2' : '2.0'}"
ActiveRecord::Base.send :include, ActiveRecord::NamedScope
end

82 changes: 82 additions & 0 deletions test/lib/named_scope/core_ext.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

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

class ActiveRecord::Base
class << self

def first(*args)
find(:first, *args)
end

def last(*args)
find(:last, *args)
end

def all(*args)
find(:all, *args)
end

private

def find_last(options)
order = options[:order]
if order
order = reverse_sql_order(order)
elsif !scoped?(:find, :order)
order = "#{table_name}.#{primary_key} DESC"
end
if scoped?(:find, :order)
scoped_order = reverse_sql_order(scope(:find, :order))
scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
end
find_initial(options.merge({ :order => order }))
end

def reverse_sql_order(order_query)
reversed_query = order_query.split(/,/).each { |s|
if s.match(/\s(asc|ASC)$/)
s.gsub!(/\s(asc|ASC)$/, ' DESC')
elsif s.match(/\s(desc|DESC)$/)
s.gsub!(/\s(desc|DESC)$/, ' ASC')
elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
s.concat(' DESC')
end
}.join(',')
end

end
end

ActiveRecord::Associations::AssociationCollection.class_eval do

def last(*args)
if fetch_first_or_last_using_find? args
find(:last, *args)
else
load_target unless loaded?
@target.last(*args)
end
end

private

def fetch_first_or_last_using_find?(args)
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
end

end
Loading

0 comments on commit ca3c363

Please sign in to comment.