Add scoping and unscoped as the syntax to replace the old with_scope and with_exclusive_scope. A few examples:
* with_scope now should be scoping: Before: Comment.with_scope(:find => { :conditions => { :post_id => 1 } }) do Comment.first #=> SELECT * FROM comments WHERE post_id = 1 end After: Comment.where(:post_id => 1).scoping do Comment.first #=> SELECT * FROM comments WHERE post_id = 1 end * with_exclusive_scope now should be unscoped: class Post < ActiveRecord::Base default_scope :published => true end Post.all #=> SELECT * FROM posts WHERE published = true Before: Post.with_exclusive_scope do Post.all #=> SELECT * FROM posts end After: Post.unscoped do Post.all #=> SELECT * FROM posts end Notice you can also use unscoped without a block and it will return an anonymous scope with default_scope values: Post.unscoped.all #=> SELECT * FROM posts
This commit is contained in:
parent
9013227e00
commit
bd1666ad1d
@ -398,7 +398,7 @@ def colorize_logging(*args)
|
||||
|
||||
delegate :find, :first, :last, :all, :destroy, :destroy_all, :exists?, :delete, :delete_all, :update, :update_all, :to => :scoped
|
||||
delegate :find_each, :find_in_batches, :to => :scoped
|
||||
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :to => :scoped
|
||||
delegate :select, :group, :order, :limit, :joins, :where, :preload, :eager_load, :includes, :from, :lock, :readonly, :having, :create_with, :to => :scoped
|
||||
delegate :count, :average, :minimum, :maximum, :sum, :calculate, :to => :scoped
|
||||
|
||||
# Executes a custom SQL query against your database and returns all the results. The results will
|
||||
@ -801,7 +801,7 @@ def column_methods_hash #:nodoc:
|
||||
def reset_column_information
|
||||
undefine_attribute_methods
|
||||
@column_names = @columns = @columns_hash = @content_columns = @dynamic_methods_hash = @inheritance_column = nil
|
||||
@arel_engine = @unscoped = @arel_table = nil
|
||||
@arel_engine = @relation = @arel_table = nil
|
||||
end
|
||||
|
||||
def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
|
||||
@ -904,9 +904,9 @@ def sti_name
|
||||
store_full_sti_class ? name : name.demodulize
|
||||
end
|
||||
|
||||
def unscoped
|
||||
@unscoped ||= Relation.new(self, arel_table)
|
||||
finder_needs_type_condition? ? @unscoped.where(type_condition) : @unscoped
|
||||
def relation
|
||||
@relation ||= Relation.new(self, arel_table)
|
||||
finder_needs_type_condition? ? @relation.where(type_condition) : @relation
|
||||
end
|
||||
|
||||
def arel_table
|
||||
@ -923,6 +923,31 @@ def arel_engine
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a scope for this class without taking into account the default_scope.
|
||||
#
|
||||
# class Post < ActiveRecord::Base
|
||||
# default_scope :published => true
|
||||
# end
|
||||
#
|
||||
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
||||
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
||||
#
|
||||
# This method also accepts a block meaning that all queries inside the block will
|
||||
# not use the default_scope:
|
||||
#
|
||||
# Post.unscoped {
|
||||
# limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
||||
# }
|
||||
#
|
||||
def unscoped
|
||||
block_given? ? relation.scoping { yield } : relation
|
||||
end
|
||||
|
||||
def scoped_methods #:nodoc:
|
||||
key = :"#{self}_scoped_methods"
|
||||
Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
|
||||
end
|
||||
|
||||
private
|
||||
# Finder methods must instantiate through this method to work with the
|
||||
# single-table inheritance model that makes it possible to create
|
||||
@ -1183,11 +1208,6 @@ def default_scope(options = {})
|
||||
self.default_scoping << construct_finder_arel(options, default_scoping.pop)
|
||||
end
|
||||
|
||||
def scoped_methods #:nodoc:
|
||||
key = :"#{self}_scoped_methods"
|
||||
Thread.current[key] = Thread.current[key].presence || self.default_scoping.dup
|
||||
end
|
||||
|
||||
def current_scoped_methods #:nodoc:
|
||||
scoped_methods.last
|
||||
end
|
||||
|
@ -25,10 +25,9 @@ module ClassMethods
|
||||
#
|
||||
# You can define a \scope that applies to all finders using
|
||||
# ActiveRecord::Base.default_scope.
|
||||
def scoped(options = {}, &block)
|
||||
def scoped(options = nil)
|
||||
if options.present?
|
||||
relation = scoped.apply_finder_options(options)
|
||||
block_given? ? relation.extending(Module.new(&block)) : relation
|
||||
scoped.apply_finder_options(options)
|
||||
else
|
||||
current_scoped_methods ? unscoped.merge(current_scoped_methods) : unscoped.clone
|
||||
end
|
||||
@ -88,18 +87,22 @@ def scopes
|
||||
# end
|
||||
def scope(name, scope_options = {}, &block)
|
||||
name = name.to_sym
|
||||
valid_scope_name?(name)
|
||||
|
||||
if !scopes[name] && respond_to?(name, true)
|
||||
logger.warn "Creating scope :#{name}. " \
|
||||
"Overwriting existing method #{self.name}.#{name}."
|
||||
end
|
||||
extension = Module.new(&block) if block_given?
|
||||
|
||||
scopes[name] = lambda do |*args|
|
||||
options = scope_options.is_a?(Proc) ? scope_options.call(*args) : scope_options
|
||||
|
||||
relation = scoped
|
||||
relation = options.is_a?(Hash) ? relation.apply_finder_options(options) : scoped.merge(options) if options
|
||||
block_given? ? relation.extending(Module.new(&block)) : relation
|
||||
relation = if options.is_a?(Hash)
|
||||
scoped.apply_finder_options(options)
|
||||
elsif options
|
||||
scoped.merge(options)
|
||||
else
|
||||
scoped
|
||||
end
|
||||
|
||||
extension ? relation.extending(extension) : relation
|
||||
end
|
||||
|
||||
singleton_class.send :define_method, name, &scopes[name]
|
||||
@ -109,7 +112,15 @@ def named_scope(*args, &block)
|
||||
ActiveSupport::Deprecation.warn("Base.named_scope has been deprecated, please use Base.scope instead", caller)
|
||||
scope(*args, &block)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def valid_scope_name?(name)
|
||||
if !scopes[name] && respond_to?(name, true)
|
||||
logger.warn "Creating scope :#{name}. " \
|
||||
"Overwriting existing method #{self.name}.#{name}."
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -182,7 +182,7 @@ def toggle!(attribute)
|
||||
def reload(options = nil)
|
||||
clear_aggregation_cache
|
||||
clear_association_cache
|
||||
@attributes.update(self.class.send(:with_exclusive_scope) { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
|
||||
@attributes.update(self.class.unscoped { self.class.find(self.id, options) }.instance_variable_get('@attributes'))
|
||||
@attributes_cache = {}
|
||||
self
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ class Relation
|
||||
attr_reader :table, :klass
|
||||
attr_accessor :extensions
|
||||
|
||||
def initialize(klass, table, &block)
|
||||
def initialize(klass, table)
|
||||
@klass, @table = klass, table
|
||||
|
||||
@implicit_readonly = nil
|
||||
@ -25,12 +25,10 @@ def initialize(klass, table, &block)
|
||||
SINGLE_VALUE_METHODS.each {|v| instance_variable_set(:"@#{v}_value", nil)}
|
||||
(ASSOCIATION_METHODS + MULTI_VALUE_METHODS).each {|v| instance_variable_set(:"@#{v}_values", [])}
|
||||
@extensions = []
|
||||
|
||||
apply_modules(Module.new(&block)) if block_given?
|
||||
end
|
||||
|
||||
def new(*args, &block)
|
||||
with_create_scope { @klass.new(*args, &block) }
|
||||
scoping { @klass.new(*args, &block) }
|
||||
end
|
||||
|
||||
def initialize_copy(other)
|
||||
@ -40,11 +38,11 @@ def initialize_copy(other)
|
||||
alias build new
|
||||
|
||||
def create(*args, &block)
|
||||
with_create_scope { @klass.create(*args, &block) }
|
||||
scoping { @klass.create(*args, &block) }
|
||||
end
|
||||
|
||||
def create!(*args, &block)
|
||||
with_create_scope { @klass.create!(*args, &block) }
|
||||
scoping { @klass.create!(*args, &block) }
|
||||
end
|
||||
|
||||
def respond_to?(method, include_private = false)
|
||||
@ -102,6 +100,25 @@ def many?
|
||||
end
|
||||
end
|
||||
|
||||
# Scope all queries to the current scope.
|
||||
#
|
||||
# ==== Example
|
||||
#
|
||||
# Comment.where(:post_id => 1).scoping do
|
||||
# Comment.first #=> SELECT * FROM comments WHERE post_id = 1
|
||||
# end
|
||||
#
|
||||
# Please check unscoped if you want to remove all previous scopes (including
|
||||
# the default_scope) during the execution of a block.
|
||||
def scoping
|
||||
@klass.scoped_methods << self
|
||||
begin
|
||||
yield
|
||||
ensure
|
||||
@klass.scoped_methods.pop
|
||||
end
|
||||
end
|
||||
|
||||
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
||||
# also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
|
||||
# database. It does not instantiate the involved models and it does not trigger Active Record callbacks
|
||||
@ -305,7 +322,6 @@ def scope_for_create
|
||||
if where.is_a?(Arel::Predicates::Equality)
|
||||
hash[where.operand1.name] = where.operand2.respond_to?(:value) ? where.operand2.value : where.operand2
|
||||
end
|
||||
|
||||
hash
|
||||
end
|
||||
end
|
||||
@ -328,15 +344,6 @@ def inspect
|
||||
to_a.inspect
|
||||
end
|
||||
|
||||
def extend(*args, &block)
|
||||
if block_given?
|
||||
apply_modules Module.new(&block)
|
||||
self
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def method_missing(method, *args, &block)
|
||||
@ -364,10 +371,6 @@ def method_missing(method, *args, &block)
|
||||
|
||||
private
|
||||
|
||||
def with_create_scope
|
||||
@klass.send(:with_scope, :create => scope_for_create, :find => {}) { yield }
|
||||
end
|
||||
|
||||
def references_eager_loaded_tables?
|
||||
# always convert table names to downcase as in Oracle quoted table names are in uppercase
|
||||
joined_tables = (tables_in_string(arel.joins(arel)) + [table.name, table.table_alias]).compact.map(&:downcase).uniq
|
||||
|
@ -86,8 +86,9 @@ def from(value = true)
|
||||
clone.tap { |r| r.from_value = value }
|
||||
end
|
||||
|
||||
def extending(*modules)
|
||||
clone.tap { |r| r.send :apply_modules, *modules }
|
||||
def extending(*modules, &block)
|
||||
modules << Module.new(&block) if block_given?
|
||||
clone.tap { |r| r.send(:apply_modules, *modules) }
|
||||
end
|
||||
|
||||
def reverse_order
|
||||
|
@ -173,7 +173,7 @@ def test_alt_find_first_within_inheritance
|
||||
|
||||
def test_complex_inheritance
|
||||
very_special_client = VerySpecialClient.create("name" => "veryspecial")
|
||||
assert_equal very_special_client, VerySpecialClient.find(:first, :conditions => "name = 'veryspecial'")
|
||||
assert_equal very_special_client, VerySpecialClient.where("name = 'veryspecial'").first
|
||||
assert_equal very_special_client, SpecialClient.find(:first, :conditions => "name = 'veryspecial'")
|
||||
assert_equal very_special_client, Company.find(:first, :conditions => "name = 'veryspecial'")
|
||||
assert_equal very_special_client, Client.find(:first, :conditions => "name = 'veryspecial'")
|
||||
|
@ -1,3 +1,7 @@
|
||||
# This file can be removed once with_exclusive_scope and with_scope are removed.
|
||||
# All the tests were already ported to relation_scoping_test.rb when the new
|
||||
# relation scoping API was added.
|
||||
|
||||
require "cases/helper"
|
||||
require 'models/post'
|
||||
require 'models/author'
|
||||
@ -540,204 +544,3 @@ def test_nested_scoped_find_merges_new_and_old_style_joins
|
||||
assert_equal authors(:david).attributes, scoped_authors.first.attributes
|
||||
end
|
||||
end
|
||||
|
||||
class HasManyScopingTest< ActiveRecord::TestCase
|
||||
fixtures :comments, :posts
|
||||
|
||||
def setup
|
||||
@welcome = Post.find(1)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_methods
|
||||
assert_equal 'a comment...', Comment.what_are_you
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_scoped
|
||||
assert_equal 4, Comment.search_by_type('Comment').size
|
||||
assert_equal 2, @welcome.comments.search_by_type('Comment').size
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 4, Comment.find_all_by_type('Comment').size
|
||||
assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
|
||||
end
|
||||
|
||||
def test_nested_scope
|
||||
Comment.send(:with_scope, :find => { :conditions => '1=1' }) do
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
|
||||
fixtures :posts, :categories, :categories_posts
|
||||
|
||||
def setup
|
||||
@welcome = Post.find(1)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_methods
|
||||
assert_equal 'a category...', Category.what_are_you
|
||||
assert_equal 'a category...', @welcome.categories.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 4, Category.find_all_by_type('SpecialCategory').size
|
||||
assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
|
||||
assert_equal 2, @welcome.categories.find_all_by_type('Category').size
|
||||
end
|
||||
|
||||
def test_nested_scope
|
||||
Category.send(:with_scope, :find => { :conditions => '1=1' }) do
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultScopingTest < ActiveRecord::TestCase
|
||||
fixtures :developers, :posts
|
||||
|
||||
def test_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_default_scope_with_conditions_string
|
||||
assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
|
||||
assert_equal nil, DeveloperCalledDavid.create!.name
|
||||
end
|
||||
|
||||
def test_default_scope_with_conditions_hash
|
||||
assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
|
||||
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
|
||||
end
|
||||
|
||||
def test_default_scoping_with_threads
|
||||
2.times do
|
||||
Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_scoping_with_inheritance
|
||||
# Inherit a class having a default scope and define a new default scope
|
||||
klass = Class.new(DeveloperOrderedBySalary)
|
||||
klass.send :default_scope, :limit => 1
|
||||
|
||||
# Scopes added on children should append to parent scope
|
||||
assert_equal 1, klass.scoped.limit_value
|
||||
assert_equal ['salary DESC'], klass.scoped.order_values
|
||||
|
||||
# Parent should still have the original scope
|
||||
assert_nil DeveloperOrderedBySalary.scoped.limit_value
|
||||
assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
|
||||
end
|
||||
|
||||
def test_default_scope_called_twice_merges_conditions
|
||||
Developer.destroy_all
|
||||
Developer.create!(:name => "David", :salary => 80000)
|
||||
Developer.create!(:name => "David", :salary => 100000)
|
||||
Developer.create!(:name => "Brian", :salary => 100000)
|
||||
|
||||
klass = Class.new(Developer)
|
||||
klass.__send__ :default_scope, :conditions => { :name => "David" }
|
||||
klass.__send__ :default_scope, :conditions => { :salary => 100000 }
|
||||
assert_equal 1, klass.count
|
||||
assert_equal "David", klass.first.name
|
||||
assert_equal 100000, klass.first.salary
|
||||
end
|
||||
def test_method_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_named_scope_overwrites_default
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
|
||||
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_exclusive_scope
|
||||
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_overwriting_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_default_scope_using_relation
|
||||
posts = PostWithComment.scoped
|
||||
assert_equal 2, posts.count
|
||||
assert_equal posts(:thinking), posts.first
|
||||
end
|
||||
|
||||
def test_create_attribute_overwrites_default_scoping
|
||||
assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
|
||||
assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
|
||||
end
|
||||
|
||||
def test_create_attribute_overwrites_default_values
|
||||
assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
|
||||
assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
|
||||
end
|
||||
end
|
||||
|
||||
=begin
|
||||
# We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
|
||||
|
||||
class BelongsToScopingTest< ActiveRecord::TestCase
|
||||
fixtures :comments, :posts
|
||||
|
||||
def setup
|
||||
@greetings = Comment.find(1)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_method
|
||||
assert_equal 'a post...', Post.what_are_you
|
||||
assert_equal 'a post...', @greetings.post.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 4, Post.find_all_by_type('Post').size
|
||||
assert_equal 1, @greetings.post.find_all_by_type('Post').size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class HasOneScopingTest< ActiveRecord::TestCase
|
||||
fixtures :comments, :posts
|
||||
|
||||
def setup
|
||||
@sti_comments = Post.find(4)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_methods
|
||||
assert_equal 'a comment...', Comment.what_are_you
|
||||
assert_equal 'a very special comment...', @sti_comments.very_special_comment.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 1, Comment.find_all_by_type('VerySpecialComment').size
|
||||
assert_equal 1, @sti_comments.very_special_comment.find_all_by_type('VerySpecialComment').size
|
||||
assert_equal 0, @sti_comments.very_special_comment.find_all_by_type('Comment').size
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=end
|
||||
|
@ -54,8 +54,8 @@ def test_scope_should_respond_to_own_methods_and_methods_of_the_proxy
|
||||
end
|
||||
|
||||
def test_respond_to_respects_include_private_parameter
|
||||
assert !Topic.approved.respond_to?(:with_create_scope)
|
||||
assert Topic.approved.respond_to?(:with_create_scope, true)
|
||||
assert !Topic.approved.respond_to?(:tables_in_string)
|
||||
assert Topic.approved.respond_to?(:tables_in_string, true)
|
||||
end
|
||||
|
||||
def test_subclasses_inherit_scopes
|
||||
|
396
activerecord/test/cases/relation_scoping_test.rb
Normal file
396
activerecord/test/cases/relation_scoping_test.rb
Normal file
@ -0,0 +1,396 @@
|
||||
require "cases/helper"
|
||||
require 'models/post'
|
||||
require 'models/author'
|
||||
require 'models/developer'
|
||||
require 'models/project'
|
||||
require 'models/comment'
|
||||
require 'models/category'
|
||||
|
||||
class RelationScopingTest < ActiveRecord::TestCase
|
||||
fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
|
||||
|
||||
def test_scoped_find
|
||||
Developer.where("name = 'David'").scoping do
|
||||
assert_nothing_raised { Developer.find(1) }
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_first
|
||||
developer = Developer.find(10)
|
||||
Developer.where("salary = 100000").scoping do
|
||||
assert_equal developer, Developer.order("name").first
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_last
|
||||
highest_salary = Developer.order("salary DESC").first
|
||||
|
||||
Developer.order("salary").scoping do
|
||||
assert_equal highest_salary, Developer.last
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_last_preserves_scope
|
||||
lowest_salary = Developer.first :order => "salary ASC"
|
||||
highest_salary = Developer.first :order => "salary DESC"
|
||||
|
||||
Developer.order("salary").scoping do
|
||||
assert_equal highest_salary, Developer.last
|
||||
assert_equal lowest_salary, Developer.first
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_combines_and_sanitizes_conditions
|
||||
Developer.where("salary = 9000").scoping do
|
||||
assert_equal developers(:poor_jamis), Developer.where("name = 'Jamis'").first
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_all
|
||||
Developer.where("name = 'David'").scoping do
|
||||
assert_equal [developers(:david)], Developer.all
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_select
|
||||
Developer.select("id, name").scoping do
|
||||
developer = Developer.where("name = 'David'").first
|
||||
assert_equal "David", developer.name
|
||||
assert !developer.has_attribute?(:salary)
|
||||
end
|
||||
end
|
||||
|
||||
def test_scope_select_concatenates
|
||||
Developer.select("id, name").scoping do
|
||||
developer = Developer.select('id, salary').where("name = 'David'").first
|
||||
assert_equal 80000, developer.salary
|
||||
assert developer.has_attribute?(:id)
|
||||
assert developer.has_attribute?(:name)
|
||||
assert developer.has_attribute?(:salary)
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_count
|
||||
Developer.where("name = 'David'").scoping do
|
||||
assert_equal 1, Developer.count
|
||||
end
|
||||
|
||||
Developer.where('salary = 100000').scoping do
|
||||
assert_equal 8, Developer.count
|
||||
assert_equal 1, Developer.where("name LIKE 'fixture_1%'").count
|
||||
end
|
||||
end
|
||||
|
||||
def test_scoped_find_include
|
||||
# with the include, will retrieve only developers for the given project
|
||||
scoped_developers = Developer.includes(:projects).scoping do
|
||||
Developer.where('projects.id = 2').all
|
||||
end
|
||||
assert scoped_developers.include?(developers(:david))
|
||||
assert !scoped_developers.include?(developers(:jamis))
|
||||
assert_equal 1, scoped_developers.size
|
||||
end
|
||||
|
||||
def test_scoped_find_joins
|
||||
scoped_developers = Developer.joins('JOIN developers_projects ON id = developer_id').scoping do
|
||||
Developer.where('developers_projects.project_id = 2').all
|
||||
end
|
||||
|
||||
assert scoped_developers.include?(developers(:david))
|
||||
assert !scoped_developers.include?(developers(:jamis))
|
||||
assert_equal 1, scoped_developers.size
|
||||
assert_equal developers(:david).attributes, scoped_developers.first.attributes
|
||||
end
|
||||
|
||||
def test_scoped_create_with_where
|
||||
new_comment = VerySpecialComment.where(:post_id => 1).scoping do
|
||||
VerySpecialComment.create :body => "Wonderful world"
|
||||
end
|
||||
|
||||
assert_equal 1, new_comment.post_id
|
||||
assert Post.find(1).comments.include?(new_comment)
|
||||
end
|
||||
|
||||
def test_scoped_create_with_create_with
|
||||
new_comment = VerySpecialComment.create_with(:post_id => 1).scoping do
|
||||
VerySpecialComment.create :body => "Wonderful world"
|
||||
end
|
||||
|
||||
assert_equal 1, new_comment.post_id
|
||||
assert Post.find(1).comments.include?(new_comment)
|
||||
end
|
||||
|
||||
def test_scoped_create_with_create_with_has_higher_priority
|
||||
new_comment = VerySpecialComment.where(:post_id => 2).create_with(:post_id => 1).scoping do
|
||||
VerySpecialComment.create :body => "Wonderful world"
|
||||
end
|
||||
|
||||
assert_equal 1, new_comment.post_id
|
||||
assert Post.find(1).comments.include?(new_comment)
|
||||
end
|
||||
|
||||
def test_ensure_that_method_scoping_is_correctly_restored
|
||||
scoped_methods = Developer.send(:current_scoped_methods)
|
||||
|
||||
begin
|
||||
Developer.where("name = 'Jamis'").scoping do
|
||||
raise "an exception"
|
||||
end
|
||||
rescue
|
||||
end
|
||||
|
||||
assert_equal scoped_methods, Developer.send(:current_scoped_methods)
|
||||
end
|
||||
end
|
||||
|
||||
class NestedRelationScopingTest < ActiveRecord::TestCase
|
||||
fixtures :authors, :developers, :projects, :comments, :posts
|
||||
|
||||
def test_merge_options
|
||||
Developer.where('salary = 80000').scoping do
|
||||
Developer.limit(10).scoping do
|
||||
devs = Developer.scoped
|
||||
assert_equal '(salary = 80000)', devs.arel.send(:where_clauses).join(' AND ')
|
||||
assert_equal 10, devs.taken
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_merge_inner_scope_has_priority
|
||||
Developer.limit(5).scoping do
|
||||
Developer.limit(10).scoping do
|
||||
assert_equal 10, Developer.count
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_replace_options
|
||||
Developer.where(:name => 'David').scoping do
|
||||
Developer.unscoped do
|
||||
assert_equal 'Jamis', Developer.where(:name => 'Jamis').first[:name]
|
||||
end
|
||||
|
||||
assert_equal 'David', Developer.first[:name]
|
||||
end
|
||||
end
|
||||
|
||||
def test_three_level_nested_exclusive_scoped_find
|
||||
Developer.where("name = 'Jamis'").scoping do
|
||||
assert_equal 'Jamis', Developer.first.name
|
||||
|
||||
Developer.unscoped.where("name = 'David'") do
|
||||
assert_equal 'David', Developer.first.name
|
||||
|
||||
Developer.unscoped.where("name = 'Maiha'") do
|
||||
assert_equal nil, Developer.first
|
||||
end
|
||||
|
||||
# ensure that scoping is restored
|
||||
assert_equal 'David', Developer.first.name
|
||||
end
|
||||
|
||||
# ensure that scoping is restored
|
||||
assert_equal 'Jamis', Developer.first.name
|
||||
end
|
||||
end
|
||||
|
||||
def test_nested_scoped_create
|
||||
comment = Comment.create_with(:post_id => 1).scoping do
|
||||
Comment.create_with(:post_id => 2).scoping do
|
||||
Comment.create :body => "Hey guys, nested scopes are broken. Please fix!"
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal 2, comment.post_id
|
||||
end
|
||||
|
||||
def test_nested_exclusive_scope_for_create
|
||||
comment = Comment.create_with(:body => "Hey guys, nested scopes are broken. Please fix!").scoping do
|
||||
Comment.unscoped.create_with(:post_id => 1).scoping do
|
||||
assert Comment.new.body.blank?
|
||||
Comment.create :body => "Hey guys"
|
||||
end
|
||||
end
|
||||
|
||||
assert_equal 1, comment.post_id
|
||||
assert_equal 'Hey guys', comment.body
|
||||
end
|
||||
end
|
||||
|
||||
class HasManyScopingTest< ActiveRecord::TestCase
|
||||
fixtures :comments, :posts
|
||||
|
||||
def setup
|
||||
@welcome = Post.find(1)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_methods
|
||||
assert_equal 'a comment...', Comment.what_are_you
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_scoped
|
||||
assert_equal 4, Comment.search_by_type('Comment').size
|
||||
assert_equal 2, @welcome.comments.search_by_type('Comment').size
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 4, Comment.find_all_by_type('Comment').size
|
||||
assert_equal 2, @welcome.comments.find_all_by_type('Comment').size
|
||||
end
|
||||
|
||||
def test_nested_scope_finder
|
||||
Comment.where('1=0').scoping do
|
||||
assert_equal 0, @welcome.comments.count
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
|
||||
Comment.where('1=1').scoping do
|
||||
assert_equal 2, @welcome.comments.count
|
||||
assert_equal 'a comment...', @welcome.comments.what_are_you
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
|
||||
fixtures :posts, :categories, :categories_posts
|
||||
|
||||
def setup
|
||||
@welcome = Post.find(1)
|
||||
end
|
||||
|
||||
def test_forwarding_of_static_methods
|
||||
assert_equal 'a category...', Category.what_are_you
|
||||
assert_equal 'a category...', @welcome.categories.what_are_you
|
||||
end
|
||||
|
||||
def test_forwarding_to_dynamic_finders
|
||||
assert_equal 4, Category.find_all_by_type('SpecialCategory').size
|
||||
assert_equal 0, @welcome.categories.find_all_by_type('SpecialCategory').size
|
||||
assert_equal 2, @welcome.categories.find_all_by_type('Category').size
|
||||
end
|
||||
|
||||
def test_nested_scope_finder
|
||||
Category.where('1=0').scoping do
|
||||
assert_equal 0, @welcome.categories.count
|
||||
assert_equal 'a category...', @welcome.categories.what_are_you
|
||||
end
|
||||
|
||||
Category.where('1=1').scoping do
|
||||
assert_equal 2, @welcome.categories.count
|
||||
assert_equal 'a category...', @welcome.categories.what_are_you
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class DefaultScopingTest < ActiveRecord::TestCase
|
||||
fixtures :developers, :posts
|
||||
|
||||
def test_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_default_scope_is_unscoped_on_find
|
||||
assert_equal 1, DeveloperCalledDavid.count
|
||||
assert_equal 11, DeveloperCalledDavid.unscoped.count
|
||||
end
|
||||
|
||||
def test_default_scope_is_unscoped_on_create
|
||||
assert_nil DeveloperCalledJamis.unscoped.create!.name
|
||||
end
|
||||
|
||||
def test_default_scope_with_conditions_string
|
||||
assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeveloperCalledDavid.find(:all).map(&:id).sort
|
||||
assert_equal nil, DeveloperCalledDavid.create!.name
|
||||
end
|
||||
|
||||
def test_default_scope_with_conditions_hash
|
||||
assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeveloperCalledJamis.find(:all).map(&:id).sort
|
||||
assert_equal 'Jamis', DeveloperCalledJamis.create!.name
|
||||
end
|
||||
|
||||
def test_default_scoping_with_threads
|
||||
2.times do
|
||||
Thread.new { assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join
|
||||
end
|
||||
end
|
||||
|
||||
def test_default_scoping_with_inheritance
|
||||
# Inherit a class having a default scope and define a new default scope
|
||||
klass = Class.new(DeveloperOrderedBySalary)
|
||||
klass.send :default_scope, :limit => 1
|
||||
|
||||
# Scopes added on children should append to parent scope
|
||||
assert_equal 1, klass.scoped.limit_value
|
||||
assert_equal ['salary DESC'], klass.scoped.order_values
|
||||
|
||||
# Parent should still have the original scope
|
||||
assert_nil DeveloperOrderedBySalary.scoped.limit_value
|
||||
assert_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values
|
||||
end
|
||||
|
||||
def test_default_scope_called_twice_merges_conditions
|
||||
Developer.destroy_all
|
||||
Developer.create!(:name => "David", :salary => 80000)
|
||||
Developer.create!(:name => "David", :salary => 100000)
|
||||
Developer.create!(:name => "Brian", :salary => 100000)
|
||||
|
||||
klass = Class.new(Developer)
|
||||
klass.__send__ :default_scope, :conditions => { :name => "David" }
|
||||
klass.__send__ :default_scope, :conditions => { :salary => 100000 }
|
||||
assert_equal 1, klass.count
|
||||
assert_equal "David", klass.first.name
|
||||
assert_equal 100000, klass.first.salary
|
||||
end
|
||||
def test_method_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_scope
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_named_scope_overwrites_default
|
||||
expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.name }
|
||||
received = DeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_nested_exclusive_scope
|
||||
expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
|
||||
DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
|
||||
end
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_overwriting_default_scope
|
||||
expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
|
||||
assert_equal expected, received
|
||||
end
|
||||
|
||||
def test_default_scope_using_relation
|
||||
posts = PostWithComment.scoped
|
||||
assert_equal 2, posts.count
|
||||
assert_equal posts(:thinking), posts.first
|
||||
end
|
||||
|
||||
def test_create_attribute_overwrites_default_scoping
|
||||
assert_equal 'David', PoorDeveloperCalledJamis.create!(:name => 'David').name
|
||||
assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
|
||||
end
|
||||
|
||||
def test_create_attribute_overwrites_default_values
|
||||
assert_equal nil, PoorDeveloperCalledJamis.create!(:salary => nil).salary
|
||||
assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary
|
||||
end
|
||||
end
|
@ -618,7 +618,7 @@ def test_except
|
||||
end
|
||||
|
||||
def test_anonymous_extension
|
||||
relation = Post.where(:author_id => 1).order('id ASC').extend do
|
||||
relation = Post.where(:author_id => 1).order('id ASC').extending do
|
||||
def author
|
||||
'lifo'
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user