Un-deprecate using 'default_scope' as a macro, but if you are calling the macro multiple times that will give deprecation warnings, and in 3.2 we will simply overwrite the default scope when you call the macro multiple times.

This commit is contained in:
Jon Leighton
2011-04-18 23:15:38 +01:00
parent 0acc6bd6cb
commit 6f84c73dc4
10 changed files with 85 additions and 270 deletions

View File

@@ -11,25 +11,34 @@
[Jon Leighton]
* Deprecated support for passing hashes and relations to 'default_scope'. Please create a class
method for your scope instead. For example, change this:
* Calling 'default_scope' multiple times in a class (including when a superclass calls
'default_scope') is deprecated. The current behavior is that this will merge the default
scopes together:
class Post < ActiveRecord::Base
class Post < ActiveRecord::Base # Rails 3.1
default_scope where(:published => true)
default_scope where(:hidden => false)
# The default scope is now: where(:published => true, :hidden => false)
end
To this:
In Rails 3.2, the behavior will be changed to overwrite previous scopes:
class Post < ActiveRecord::Base # Rails 3.2
default_scope where(:published => true)
default_scope where(:hidden => false)
# The default scope is now: where(:hidden => false)
end
If you wish to merge default scopes in special ways, it is recommended to define your default
scope as a class method and use the standard techniques for sharing code (inheritance, mixins,
etc.):
class Post < ActiveRecord::Base
def self.default_scope
where(:published => true)
where(:published => true).where(:hidden => false)
end
end
Rationale: It will make the implementation simpler because we can simply use inheritance to
handle inheritance scenarios, rather than trying to make up our own rules about what should
happen when you call default_scope multiple times or in subclasses.
[Jon Leighton]
* PostgreSQL adapter only supports PostgreSQL version 8.2 and higher.

View File

@@ -1177,13 +1177,11 @@ MSG
Thread.current[:"#{self}_current_scope"] = scope
end
# Implement this method in your model to set a default scope for all operations on
# Use this macro in your model to set a default scope for all operations on
# the model.
#
# class Person < ActiveRecord::Base
# def self.default_scope
# order('last_name, first_name')
# end
# default_scope order('last_name, first_name')
# end
#
# Person.all # => SELECT * FROM people ORDER BY last_name, first_name
@@ -1192,39 +1190,48 @@ MSG
# applied while updating a record.
#
# class Article < ActiveRecord::Base
# def self.default_scope
# where(:published => true)
# end
# default_scope where(:published => true)
# end
#
# Article.new.published # => true
# Article.create.published # => true
#
# === Deprecation warning
# If you need to do more complex things with a default scope, you can alternatively
# define it as a class method:
#
# There is an alternative syntax as follows:
#
# class Person < ActiveRecord::Base
# default_scope order('last_name, first_name')
# class Article < ActiveRecord::Base
# def self.default_scope
# # Should return a scope, you can call 'super' here etc.
# end
# end
#
# This is now deprecated and will be removed in Rails 3.2.
def default_scope(scope = {})
ActiveSupport::Deprecation.warn <<-WARN
Passing a hash or scope to default_scope is deprecated and will be removed in Rails 3.2. You should create a class method for your scope instead. For example, change this:
if default_scopes.length != 0
ActiveSupport::Deprecation.warn <<-WARN
Calling 'default_scope' multiple times in a class (including when a superclass calls 'default_scope') is deprecated. The current behavior is that this will merge the default scopes together:
class Post < ActiveRecord::Base
class Post < ActiveRecord::Base # Rails 3.1
default_scope where(:published => true)
default_scope where(:hidden => false)
# The default scope is now: where(:published => true, :hidden => false)
end
To this:
In Rails 3.2, the behavior will be changed to overwrite previous scopes:
class Post < ActiveRecord::Base # Rails 3.2
default_scope where(:published => true)
default_scope where(:hidden => false)
# The default scope is now: where(:hidden => false)
end
If you wish to merge default scopes in special ways, it is recommended to define your default scope as a class method and use the standard techniques for sharing code (inheritance, mixins, etc.):
class Post < ActiveRecord::Base
def self.default_scope
where(:published => true)
where(:published => true).where(:hidden => false)
end
end
WARN
WARN
end
self.default_scopes = default_scopes.dup << scope
end

View File

@@ -308,6 +308,10 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal expected, received
end
def test_default_scope_as_class_method
assert_equal [developers(:david).becomes(ClassMethodDeveloperCalledDavid)], ClassMethodDeveloperCalledDavid.all
end
def test_default_scope_is_unscoped_on_find
assert_equal 1, DeveloperCalledDavid.count
assert_equal 11, DeveloperCalledDavid.unscoped.count
@@ -339,6 +343,12 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert_equal 50000, wheres[:salary]
end
def test_default_scope_with_multiple_calls
wheres = MultiplePoorDeveloperCalledJamis.scoped.where_values_hash
assert_equal "Jamis", wheres[:name]
assert_equal 50000, wheres[:salary]
end
def test_method_scope
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
@@ -434,175 +444,18 @@ class DefaultScopingTest < ActiveRecord::TestCase
assert DeveloperCalledJamis.unscoped.poor.include?(developers(:david).becomes(DeveloperCalledJamis))
assert_equal 10, DeveloperCalledJamis.unscoped.poor.length
end
end
class DeprecatedDefaultScopingTest < ActiveRecord::TestCase
fixtures :developers, :posts
def test_multiple_default_scope_calls_are_deprecated
klass = Class.new(ActiveRecord::Base)
def test_default_scope
expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
received = DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
assert_equal expected, received
end
def test_default_scope_is_unscoped_on_find
assert_equal 1, DeprecatedDeveloperCalledDavid.count
assert_equal 11, DeprecatedDeveloperCalledDavid.unscoped.count
end
def test_default_scope_is_unscoped_on_create
assert_nil DeprecatedDeveloperCalledJamis.unscoped.create!.name
end
def test_default_scope_with_conditions_string
assert_equal Developer.find_all_by_name('David').map(&:id).sort, DeprecatedDeveloperCalledDavid.find(:all).map(&:id).sort
assert_equal nil, DeprecatedDeveloperCalledDavid.create!.name
end
def test_default_scope_with_conditions_hash
assert_equal Developer.find_all_by_name('Jamis').map(&:id).sort, DeprecatedDeveloperCalledJamis.find(:all).map(&:id).sort
assert_equal 'Jamis', DeprecatedDeveloperCalledJamis.create!.name
end
def test_default_scoping_with_threads
2.times do
Thread.new { assert DeprecatedDeveloperOrderedBySalary.scoped.to_sql.include?('salary DESC') }.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(DeprecatedDeveloperOrderedBySalary)
ActiveSupport::Deprecation.silence { klass.send :default_scope, :limit => 1 }
# Scopes added on children should append to parent scope
assert_equal [developers(:jamis).id], klass.all.map(&:id)
# Parent should still have the original scope
assert_equal Developer.order('salary DESC').map(&:id), DeprecatedDeveloperOrderedBySalary.all.map(&:id)
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)
ActiveSupport::Deprecation.silence do
klass.__send__ :default_scope, :conditions => { :name => "David" }
klass.__send__ :default_scope, :conditions => { :salary => 100000 }
end
assert_equal 1, klass.count
assert_equal "David", klass.first.name
assert_equal 100000, klass.first.salary
end
def test_default_scope_called_twice_in_different_place_merges_where_clause
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)
ActiveSupport::Deprecation.silence do
klass.class_eval do
default_scope where("name = 'David'")
default_scope where("salary = 100000")
end
assert_not_deprecated do
klass.send(:default_scope, :foo => :bar)
end
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 => 'salary DESC, name DESC').collect { |dev| dev.salary }
received = DeprecatedDeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
assert_equal expected, received
end
def test_nested_scope
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.salary }
received = DeprecatedDeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do
DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
assert_deprecated do
klass.send(:default_scope, :foo => :bar)
end
assert_equal expected, received
end
def test_scope_overwrites_default
expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name }
received = DeprecatedDeveloperOrderedBySalary.by_name.find(:all).collect { |dev| dev.name }
assert_equal expected, received
end
def test_reorder_overrides_default_scope_order
expected = Developer.order('name DESC').collect { |dev| dev.name }
received = DeprecatedDeveloperOrderedBySalary.reorder('name DESC').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 = DeprecatedDeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do
DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
end
assert_equal expected, received
end
def test_order_in_default_scope_should_prevail
expected = Developer.find(:all, :order => 'salary desc').collect { |dev| dev.salary }
received = DeprecatedDeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
assert_equal expected, received
end
def test_default_scope_using_relation
posts = DeprecatedPostWithComment.scoped
assert_equal 2, posts.to_a.length
assert_equal posts(:thinking), posts.first
end
def test_create_attribute_overwrites_default_scoping
assert_equal 'David', DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').name
assert_equal 200000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary
end
def test_create_attribute_overwrites_default_values
assert_equal nil, DeprecatedPoorDeveloperCalledJamis.create!(:salary => nil).salary
assert_equal 50000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').salary
end
def test_default_scope_attribute
jamis = DeprecatedPoorDeveloperCalledJamis.new(:name => 'David')
assert_equal 50000, jamis.salary
end
def test_where_attribute
aaron = DeprecatedPoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron')
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
end
def test_where_attribute_merge
aaron = DeprecatedPoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron')
assert_equal 'Aaron', aaron.name
end
def test_create_with_merge
aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge(
DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
aaron = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).
create_with(:name => 'Aaron').new
assert_equal 20, aaron.salary
assert_equal 'Aaron', aaron.name
end
def test_create_with_reset
jamis = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new
assert_equal 'Jamis', jamis.name
assert_equal 2, klass.default_scopes.length
end
end

View File

@@ -1,8 +1,5 @@
class Bulb < ActiveRecord::Base
def self.default_scope
where :name => 'defaulty'
end
default_scope where(:name => 'defaulty')
belongs_to :car
attr_reader :scope_after_initialize

View File

@@ -15,13 +15,9 @@ class Car < ActiveRecord::Base
end
class CoolCar < Car
def self.default_scope
order 'name desc'
end
default_scope :order => 'name desc'
end
class FastCar < Car
def self.default_scope
order 'name desc'
end
default_scope :order => 'name desc'
end

View File

@@ -12,10 +12,7 @@ end
class SpecialCategorization < ActiveRecord::Base
self.table_name = 'categorizations'
def self.default_scope
where(:special => true)
end
default_scope where(:special => true)
belongs_to :author
belongs_to :category

View File

@@ -86,10 +86,7 @@ end
class DeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
def self.default_scope
order('salary DESC')
end
default_scope :order => 'salary DESC'
scope :by_name, order('name DESC')
@@ -102,74 +99,41 @@ end
class DeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
default_scope where("name = 'David'")
end
class ClassMethodDeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
def self.default_scope
where "name = 'David'"
where(:name => 'David')
end
end
class DeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
def self.default_scope
where :name => 'Jamis'
end
default_scope where(:name => 'Jamis')
scope :poor, where('salary < 150000')
end
class AbstractDeveloperCalledJamis < ActiveRecord::Base
self.abstract_class = true
def self.default_scope
where :name => 'Jamis'
end
end
class PoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
def self.default_scope
where :name => 'Jamis', :salary => 50000
end
default_scope where(:name => 'Jamis', :salary => 50000)
end
class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis
self.table_name = 'developers'
def self.default_scope
super.where :salary => 50000
ActiveSupport::Deprecation.silence do
default_scope where(:salary => 50000)
end
end
ActiveSupport::Deprecation.silence do
class DeprecatedDeveloperOrderedBySalary < ActiveRecord::Base
self.table_name = 'developers'
default_scope :order => 'salary DESC'
class MultiplePoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
default_scope where(:name => 'Jamis')
def self.by_name
order('name DESC')
end
def self.all_ordered_by_name
with_scope(:find => { :order => 'name DESC' }) do
find(:all)
end
end
end
class DeprecatedDeveloperCalledDavid < ActiveRecord::Base
self.table_name = 'developers'
default_scope :conditions => "name = 'David'"
end
class DeprecatedDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
default_scope :conditions => { :name => 'Jamis' }
end
class DeprecatedPoorDeveloperCalledJamis < ActiveRecord::Base
self.table_name = 'developers'
default_scope :conditions => { :name => 'Jamis', :salary => 50000 }
ActiveSupport::Deprecation.silence do
default_scope where(:salary => 50000)
end
end

View File

@@ -157,10 +157,7 @@ end
class FirstPost < ActiveRecord::Base
self.table_name = 'posts'
def self.default_scope
where(:id => 1)
end
default_scope where(:id => 1)
has_many :comments, :foreign_key => :post_id
has_one :comment, :foreign_key => :post_id

View File

@@ -19,8 +19,5 @@ end
class BadReference < ActiveRecord::Base
self.table_name = 'references'
def self.default_scope
where :favourite => false
end
default_scope where(:favourite => false)
end

View File

@@ -1,5 +1,3 @@
class WithoutTable < ActiveRecord::Base
def self.default_scope
where(:published => true)
end
default_scope where(:published => true)
end