提交 88de6b2d 编写于 作者: P Pratik Naik

Inherit named scope class Scope from Relation

上级 c6850d83
...@@ -403,8 +403,6 @@ def method_missing(method, *args) ...@@ -403,8 +403,6 @@ def method_missing(method, *args)
else else
super super
end end
elsif @reflection.klass.scopes.include?(method)
@reflection.klass.scopes[method].call(self, *args)
else else
with_scope(construct_scope) do with_scope(construct_scope) do
if block_given? if block_given?
......
...@@ -24,7 +24,7 @@ module ClassMethods ...@@ -24,7 +24,7 @@ module ClassMethods
# You can define a scope that applies to all finders using ActiveRecord::Base.default_scope. # You can define a scope that applies to all finders using ActiveRecord::Base.default_scope.
def scoped(options = {}, &block) def scoped(options = {}, &block)
if options.present? if options.present?
Scope.new(self, options, &block) Scope.init(self, options, &block)
else else
current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.spawn
end end
...@@ -105,7 +105,7 @@ def named_scope(name, options = {}, &block) ...@@ -105,7 +105,7 @@ def named_scope(name, options = {}, &block)
end end
scopes[name] = lambda do |parent_scope, *args| scopes[name] = lambda do |parent_scope, *args|
Scope.new(parent_scope, case options Scope.init(parent_scope, case options
when Hash, Relation when Hash, Relation
options options
when Proc when Proc
...@@ -120,117 +120,83 @@ def named_scope(name, options = {}, &block) ...@@ -120,117 +120,83 @@ def named_scope(name, options = {}, &block)
end end
end end
class Scope class Scope < Relation
attr_reader :klass, :proxy_options, :current_scoped_methods_when_defined attr_accessor :current_scoped_methods_when_defined
NON_DELEGATE_METHODS = %w(nil? send object_id class extend find size count sum average maximum minimum paginate first last empty? any? many? respond_to?).to_set
[].methods.each do |m|
unless m =~ /^__/ || NON_DELEGATE_METHODS.include?(m.to_s)
delegate m, :to => :proxy_found
end
end
delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass delegate :scopes, :with_scope, :with_exclusive_scope, :scoped_methods, :scoped, :to => :klass
delegate :new, :build, :all, :to => :relation
def initialize(klass, options, &block) def self.init(klass, options, &block)
extend Module.new(&block) if block_given? relation = new(klass, klass.arel_table)
options ||= {} scope = if options.is_a?(Hash)
if options.is_a?(Hash) klass.scoped.apply_finder_options(options.except(:extend))
Array.wrap(options[:extend]).each {|extension| extend extension }
@proxy_options = options.except(:extend)
else else
@proxy_options = options options ? klass.scoped.merge(options) : klass.scoped
end end
unless Scope === klass relation = relation.merge(scope)
@current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
end
@klass = klass Array.wrap(options[:extend]).each {|extension| relation.send(:extend, extension) } if options.is_a?(Hash)
end relation.send(:extend, Module.new(&block)) if block_given?
def reload relation.current_scoped_methods_when_defined = klass.send(:current_scoped_methods)
load_found; self relation
end end
def first(*args) def find(*args)
if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) options = args.extract_options!
proxy_found.first(*args) relation = options.present? ? apply_finder_options(options) : self
else
find(:first, *args)
end
end
def last(*args) case args.first
if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash)) when :first, :last, :all
proxy_found.last(*args) relation.send(args.first)
else else
find(:last, *args) options.present? ? relation.find(*args) : super
end end
end end
def size def first(*args)
@found ? @found.length : count if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
end to_a.first(*args)
def empty?
@found ? @found.empty? : count.zero?
end
def respond_to?(method, include_private = false)
super || @klass.respond_to?(method, include_private)
end
def any?
if block_given?
proxy_found.any? { |*block_args| yield(*block_args) }
else else
!empty? args.first.present? ? apply_finder_options(args.first).first : super
end end
end end
# Returns true if the named scope has more than 1 matching record. def last(*args)
def many? if args.first.kind_of?(Integer) || (loaded? && !args.first.kind_of?(Hash))
if block_given? to_a.last(*args)
proxy_found.many? { |*block_args| yield(*block_args) }
else else
size > 1 args.first.present? ? apply_finder_options(args.first).last : super
end end
end end
def relation def count(*args)
@relation ||= begin options = args.extract_options!
if proxy_options.is_a?(Hash) options.present? ? apply_finder_options(options).count(*args) : super
scoped.apply_finder_options(proxy_options)
else
scoped.merge(proxy_options)
end
end
end end
def proxy_found def ==(other)
@found || load_found to_a == other.to_a
end end
private private
def method_missing(method, *args, &block) def method_missing(method, *args, &block)
with_scope(relation, :reverse_merge) do if klass.respond_to?(method)
if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method) with_scope(self) do
with_scope current_scoped_methods_when_defined do if current_scoped_methods_when_defined && !scoped_methods.include?(current_scoped_methods_when_defined) && !scopes.include?(method)
with_scope(current_scoped_methods_when_defined) { klass.send(method, *args, &block) }
else
klass.send(method, *args, &block) klass.send(method, *args, &block)
end end
else
klass.send(method, *args, &block)
end end
else
super
end end
end end
def load_found
@found = find(:all)
end
end end
end end
end end
...@@ -7,7 +7,7 @@ class Relation ...@@ -7,7 +7,7 @@ class Relation
include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods include FinderMethods, CalculationMethods, SpawnMethods, QueryMethods
delegate :length, :collect, :map, :each, :all?, :to => :to_a delegate :length, :collect, :map, :each, :all?, :include?, :to => :to_a
attr_reader :table, :klass attr_reader :table, :klass
...@@ -175,6 +175,8 @@ def method_missing(method, *args, &block) ...@@ -175,6 +175,8 @@ def method_missing(method, *args, &block)
end end
end end
private
def with_create_scope def with_create_scope
@klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield } @klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
end end
......
...@@ -24,7 +24,7 @@ def build_from_hash(attributes, default_table) ...@@ -24,7 +24,7 @@ def build_from_hash(attributes, default_table)
case value case value
when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope when Array, ActiveRecord::Associations::AssociationCollection, ActiveRecord::NamedScope::Scope
attribute.in(value) attribute.in(value.to_a)
when Range when Range
# TODO : Arel should handle ranges with excluded end. # TODO : Arel should handle ranges with excluded end.
if value.exclude_end? if value.exclude_end?
......
module ActiveRecord module ActiveRecord
module SpawnMethods module SpawnMethods
def spawn(arel_table = self.table) def spawn(arel_table = self.table)
relation = Relation.new(@klass, arel_table) relation = self.class.new(@klass, arel_table)
(Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method| (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |query_method|
relation.send(:"#{query_method}_values=", send(:"#{query_method}_values")) relation.send(:"#{query_method}_values=", send(:"#{query_method}_values"))
...@@ -64,7 +64,7 @@ def merge(r) ...@@ -64,7 +64,7 @@ def merge(r)
alias :& :merge alias :& :merge
def except(*skips) def except(*skips)
result = Relation.new(@klass, table) result = self.class.new(@klass, table)
(Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method| (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method) result.send(:"#{method}_values=", send(:"#{method}_values")) unless skips.include?(method)
...@@ -78,7 +78,7 @@ def except(*skips) ...@@ -78,7 +78,7 @@ def except(*skips)
end end
def only(*onlies) def only(*onlies)
result = Relation.new(@klass, table) result = self.class.new(@klass, table)
onlies.each do |only| onlies.each do |only|
if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only) if (Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS).include?(only)
......
...@@ -31,7 +31,7 @@ def test_found_items_are_cached ...@@ -31,7 +31,7 @@ def test_found_items_are_cached
def test_reload_expires_cache_of_found_items def test_reload_expires_cache_of_found_items
all_posts = Topic.base all_posts = Topic.base
all_posts.inspect all_posts.all
new_post = Topic.create! new_post = Topic.create!
assert !all_posts.include?(new_post) assert !all_posts.include?(new_post)
...@@ -48,14 +48,14 @@ def test_delegates_finds_and_calculations_to_the_base_class ...@@ -48,14 +48,14 @@ def test_delegates_finds_and_calculations_to_the_base_class
end end
def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
assert Topic.approved.respond_to?(:proxy_found) assert Topic.approved.respond_to?(:limit)
assert Topic.approved.respond_to?(:count) assert Topic.approved.respond_to?(:count)
assert Topic.approved.respond_to?(:length) assert Topic.approved.respond_to?(:length)
end end
def test_respond_to_respects_include_private_parameter def test_respond_to_respects_include_private_parameter
assert !Topic.approved.respond_to?(:load_found) assert !Topic.approved.respond_to?(:with_create_scope)
assert Topic.approved.respond_to?(:load_found, true) assert Topic.approved.respond_to?(:with_create_scope, true)
end end
def test_subclasses_inherit_scopes def test_subclasses_inherit_scopes
...@@ -155,7 +155,7 @@ def test_named_scopes_honor_current_scopes_from_when_defined ...@@ -155,7 +155,7 @@ def test_named_scopes_honor_current_scopes_from_when_defined
assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5) assert_not_equal Post.ranked_by_comments.limit_by(5), authors(:david).posts.ranked_by_comments.limit_by(5)
assert_not_equal Post.top(5), authors(:david).posts.top(5) assert_not_equal Post.top(5), authors(:david).posts.top(5)
# Oracle sometimes sorts differently if WHERE condition is changed # Oracle sometimes sorts differently if WHERE condition is changed
assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).sort_by(&:id), authors(:david).posts.top(5).sort_by(&:id) assert_equal authors(:david).posts.ranked_by_comments.limit_by(5).to_a.sort_by(&:id), authors(:david).posts.top(5).to_a.sort_by(&:id)
assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5) assert_equal Post.ranked_by_comments.limit_by(5), Post.top(5)
end end
...@@ -171,11 +171,6 @@ def test_active_records_have_scope_named__scoped__ ...@@ -171,11 +171,6 @@ def test_active_records_have_scope_named__scoped__
assert_equal Topic.find(:all, scope), Topic.scoped(scope) assert_equal Topic.find(:all, scope), Topic.scoped(scope)
end end
def test_proxy_options
expected_proxy_options = { :conditions => { :approved => true } }
assert_equal expected_proxy_options, Topic.approved.proxy_options
end
def test_first_and_last_should_support_find_options def test_first_and_last_should_support_find_options
assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title') assert_equal Topic.base.first(:order => 'title'), Topic.base.find(:first, :order => 'title')
assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title') assert_equal Topic.base.last(:order => 'title'), Topic.base.find(:last, :order => 'title')
...@@ -297,7 +292,7 @@ def test_should_build_with_proxy_options_chained ...@@ -297,7 +292,7 @@ def test_should_build_with_proxy_options_chained
end end
def test_find_all_should_behave_like_select def test_find_all_should_behave_like_select
assert_equal Topic.base.select(&:approved), Topic.base.find_all(&:approved) assert_equal Topic.base.to_a.select(&:approved), Topic.base.to_a.find_all(&:approved)
end end
def test_rand_should_select_a_random_object_from_proxy def test_rand_should_select_a_random_object_from_proxy
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册