From 5740d4ec0c16d68b82f440e74fd8b74ae3fe48e6 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Mon, 4 Apr 2011 00:07:45 +0100 Subject: [PATCH] Deprecated support for passing hashes and relations to default_scope, in favour of defining a 'default_scope' class method in the model. See the CHANGELOG for more details. --- activerecord/CHANGELOG | 21 ++ activerecord/lib/active_record/base.rb | 144 +++++++---- activerecord/lib/active_record/named_scope.rb | 2 +- activerecord/lib/active_record/relation.rb | 7 +- .../has_many_associations_test.rb | 14 +- .../associations/has_one_associations_test.rb | 14 +- activerecord/test/cases/base_test.rb | 12 +- .../test/cases/method_scoping_test.rb | 12 +- .../test/cases/relation_scoping_test.rb | 231 +++++++++++++----- activerecord/test/models/bulb.rb | 13 +- activerecord/test/models/car.rb | 9 +- activerecord/test/models/categorization.rb | 4 +- activerecord/test/models/developer.rb | 69 +++++- activerecord/test/models/pirate.rb | 2 +- activerecord/test/models/post.rb | 15 +- activerecord/test/models/reference.rb | 7 +- activerecord/test/models/without_table.rb | 6 +- 17 files changed, 410 insertions(+), 172 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 88c2562619b84..64be577624c65 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,26 @@ *Rails 3.1.0 (unreleased)* +* Deprecated support for passing hashes and relations to 'default_scope'. Please create a class + method for your scope instead. For example, change this: + + class Post < ActiveRecord::Base + default_scope where(:published => true) + end + + To this: + + class Post < ActiveRecord::Base + def self.default_scope + where(:published => true) + 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. * ConnectionManagement middleware is changed to clean up the connection pool diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index fe81c7dc2f122..e0fb94c96e515 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -425,8 +425,8 @@ class Base self.store_full_sti_class = true # Stores the default scope for the class - class_attribute :default_scoping, :instance_writer => false - self.default_scoping = [] + class_attribute :default_scopes, :instance_writer => false + self.default_scopes = [] # Returns a hash of all the attributes that have been specified for serialization as # keys and their class restriction as values. @@ -870,7 +870,9 @@ def arel_engine # Returns a scope for this class without taking into account the default_scope. # # class Post < ActiveRecord::Base - # default_scope :published => true + # def self.default_scope + # where :published => true + # end # end # # Post.all # Fires "SELECT * FROM posts WHERE published = true" @@ -892,13 +894,8 @@ def unscoped #:nodoc: 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 - def before_remove_const #:nodoc: - reset_scoped_methods + self.current_scope = nil end # Specifies how the record is loaded by +Marshal+. @@ -1020,7 +1017,7 @@ def method_missing(method_id, *arguments, &block) super unless all_attributes_exists?(attribute_names) if match.finder? options = arguments.extract_options! - relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped + relation = options.any? ? scoped(options) : scoped relation.send :find_by_attributes, match, attribute_names, *arguments elsif match.instantiator? scoped.send :find_or_instantiator_by_attributes, match, attribute_names, *arguments, &block @@ -1109,43 +1106,48 @@ def all_attributes_exists?(attribute_names) # end # # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+. - def with_scope(method_scoping = {}, action = :merge, &block) - method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping) + def with_scope(scope = {}, action = :merge, &block) + # If another Active Record class has been passed in, get its current scope + scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope) + + # Cannot use self.current_scope, because that could trigger build_default_scope, which + # could in turn result in with_scope being called and hence an infinite loop. + previous_scope = Thread.current[:"#{self}_current_scope"] - if method_scoping.is_a?(Hash) + if scope.is_a?(Hash) # Dup first and second level of hash (method and params). - method_scoping = method_scoping.dup - method_scoping.each do |method, params| - method_scoping[method] = params.dup unless params == true + scope = scope.dup + scope.each do |method, params| + scope[method] = params.dup unless params == true end - method_scoping.assert_valid_keys([ :find, :create ]) - relation = construct_finder_arel(method_scoping[:find] || {}) + scope.assert_valid_keys([ :find, :create ]) + relation = construct_finder_arel(scope[:find] || {}) - if current_scoped_methods && current_scoped_methods.create_with_value && method_scoping[:create] + if previous_scope && previous_scope.create_with_value && scope[:create] scope_for_create = if action == :merge - current_scoped_methods.create_with_value.merge(method_scoping[:create]) + previous_scope.create_with_value.merge(scope[:create]) else - method_scoping[:create] + scope[:create] end relation = relation.create_with(scope_for_create) else - scope_for_create = method_scoping[:create] - scope_for_create ||= current_scoped_methods.create_with_value if current_scoped_methods + scope_for_create = scope[:create] + scope_for_create ||= previous_scope.create_with_value if previous_scope relation = relation.create_with(scope_for_create) if scope_for_create end - method_scoping = relation + scope = relation end - method_scoping = current_scoped_methods.merge(method_scoping) if current_scoped_methods && action == :merge + scope = previous_scope.merge(scope) if previous_scope && action == :merge - self.scoped_methods << method_scoping + self.current_scope = scope begin yield ensure - self.scoped_methods.pop + self.current_scope = previous_scope end end @@ -1168,39 +1170,82 @@ def with_exclusive_scope(method_scoping = {}, &block) with_scope(method_scoping, :overwrite, &block) end - # Sets the default options for the model. The format of the - # options argument is the same as in find. + def current_scope #:nodoc: + Thread.current[:"#{self}_current_scope"] ||= build_default_scope + end + + def current_scope=(scope) #:nodoc: + Thread.current[:"#{self}_current_scope"] = scope + end + + # Implement this method in your model to set a default scope for all operations on + # the model. # # class Person < ActiveRecord::Base - # default_scope order('last_name, first_name') + # def self.default_scope + # order('last_name, first_name') + # end # end # - # default_scope is also applied while creating/building a record. It is not + # Person.all # => SELECT * FROM people ORDER BY last_name, first_name + # + # The default_scope is also applied while creating/building a record. It is not # applied while updating a record. # # class Article < ActiveRecord::Base - # default_scope where(:published => true) + # def self.default_scope + # where(:published => true) + # end # end # # Article.new.published # => true # Article.create.published # => true - def default_scope(options = {}) - reset_scoped_methods - default_scoping = self.default_scoping.dup - self.default_scoping = default_scoping << construct_finder_arel(options, default_scoping.pop) - end + # + # === Deprecation warning + # + # There is an alternative syntax as follows: + # + # class Person < ActiveRecord::Base + # default_scope order('last_name, first_name') + # 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: - def current_scoped_methods #:nodoc: - method = scoped_methods.last - if method.respond_to?(:call) - relation.scoping { method.call } - else - method - end +class Post < ActiveRecord::Base + default_scope where(:published => true) +end + +To this: + +class Post < ActiveRecord::Base + def self.default_scope + where(:published => true) + end +end +WARN + + # Reset the current scope as it may contain scopes based on a now-invalid default scope + self.current_scope = nil + self.default_scopes = default_scopes.dup << scope end - def reset_scoped_methods #:nodoc: - Thread.current[:"#{self}_scoped_methods"] = nil + def build_default_scope #:nodoc: + if method(:default_scope).owner != Base.singleton_class + # Exclusively scope to just the relation, to avoid infinite recursion where the + # default scope tries to use the default scope tries to use the default scope... + relation.scoping { default_scope } + elsif default_scopes.any? + default_scopes.inject(relation) do |default_scope, scope| + if scope.is_a?(Hash) + default_scope.apply_finder_options(scope) + else + default_scope.merge(scope) + end + end + end end # Returns the class type of the record using the current module as a prefix. So descendants of @@ -1916,11 +1961,8 @@ def convert_number_column_value(value) end def populate_with_current_scope_attributes - if scope = self.class.send(:current_scoped_methods) - create_with = scope.scope_for_create - create_with.each { |att,value| - respond_to?("#{att}=") && send("#{att}=", value) - } + self.class.scoped.scope_for_create.each do |att,value| + respond_to?("#{att}=") && send("#{att}=", value) end end diff --git a/activerecord/lib/active_record/named_scope.rb b/activerecord/lib/active_record/named_scope.rb index d5fff65303779..a398476dd6e6b 100644 --- a/activerecord/lib/active_record/named_scope.rb +++ b/activerecord/lib/active_record/named_scope.rb @@ -35,7 +35,7 @@ def scoped(options = nil) if options scoped.apply_finder_options(options) else - current_scoped_methods ? relation.merge(current_scoped_methods) : relation.clone + (current_scope || relation).clone end end diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb index 75269c85d97b1..645ad0fce1443 100644 --- a/activerecord/lib/active_record/relation.rb +++ b/activerecord/lib/active_record/relation.rb @@ -154,12 +154,7 @@ def many? # 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 + @klass.send(:with_scope, self, :overwrite) { yield } end # Updates all records with details given if they match a set of conditions supplied, limits and order can diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 16d4877fe8031..007f11b535de7 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -70,16 +70,16 @@ def test_create_from_association_should_respect_default_scope # would be convenient), because this would cause that scope to be applied to any callbacks etc. def test_build_and_create_should_not_happen_within_scope car = cars(:honda) - original_scoped_methods = Bulb.scoped_methods + scoped_count = car.foo_bulbs.scoped.where_values.count - bulb = car.bulbs.build - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = car.foo_bulbs.build + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count - bulb = car.bulbs.create - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = car.foo_bulbs.create + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count - bulb = car.bulbs.create! - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = car.foo_bulbs.create! + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count end def test_no_sql_should_be_fired_if_association_already_loaded diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index c1dad5e246592..f3c96ccbe6f14 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -165,16 +165,16 @@ def test_successful_build_association def test_build_and_create_should_not_happen_within_scope pirate = pirates(:blackbeard) - original_scoped_methods = Bulb.scoped_methods.dup + scoped_count = pirate.association(:foo_bulb).scoped.where_values.count - bulb = pirate.build_bulb - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = pirate.build_foo_bulb + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count - bulb = pirate.create_bulb - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = pirate.create_foo_bulb + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count - bulb = pirate.create_bulb! - assert_equal original_scoped_methods, bulb.scoped_methods_after_initialize + bulb = pirate.create_foo_bulb! + assert_not_equal scoped_count, bulb.scope_after_initialize.where_values.count end def test_create_association diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index b3facf50b8f3e..73887f9b49b1d 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -1630,14 +1630,18 @@ def test_default_scope_is_reset Object.const_set :UnloadablePost, Class.new(ActiveRecord::Base) UnloadablePost.table_name = 'posts' UnloadablePost.class_eval do - default_scope order('posts.comments_count ASC') + class << self + def default_scope + order('posts.comments_count ASC') + end + end end - UnloadablePost.scoped_methods # make Thread.current[:UnloadablePost_scoped_methods] not nil + UnloadablePost.scoped # make Thread.current[:UnloadablePost_scoped_methods] not nil UnloadablePost.unloadable - assert_not_nil Thread.current[:UnloadablePost_scoped_methods] + assert_not_nil Thread.current[:UnloadablePost_current_scope] ActiveSupport::Dependencies.remove_unloadable_constants! - assert_nil Thread.current[:UnloadablePost_scoped_methods] + assert_nil Thread.current[:UnloadablePost_current_scope] ensure Object.class_eval{ remove_const :UnloadablePost } if defined?(UnloadablePost) end diff --git a/activerecord/test/cases/method_scoping_test.rb b/activerecord/test/cases/method_scoping_test.rb index 7e8383da9edf4..7f0f007a70e4e 100644 --- a/activerecord/test/cases/method_scoping_test.rb +++ b/activerecord/test/cases/method_scoping_test.rb @@ -249,22 +249,21 @@ def test_immutable_scope end def test_scoped_with_duck_typing - scoping = Struct.new(:method_scoping).new(:find => { :conditions => ["name = ?", 'David'] }) + scoping = Struct.new(:current_scope).new(:find => { :conditions => ["name = ?", 'David'] }) Developer.send(:with_scope, scoping) do assert_equal %w(David), Developer.find(:all).map { |d| d.name } end end def test_ensure_that_method_scoping_is_correctly_restored - scoped_methods = Developer.instance_eval('current_scoped_methods') - begin Developer.send(:with_scope, :find => { :conditions => "name = 'Jamis'" }) do raise "an exception" end rescue end - assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods') + + assert !Developer.scoped.where_values.include?("name = 'Jamis'") end end @@ -509,14 +508,15 @@ def test_immutable_merged_scope def test_ensure_that_method_scoping_is_correctly_restored Developer.send(:with_scope, :find => { :conditions => "name = 'David'" }) do - scoped_methods = Developer.instance_eval('current_scoped_methods') begin Developer.send(:with_scope, :find => { :conditions => "name = 'Maiha'" }) do raise "an exception" end rescue end - assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods') + + assert Developer.scoped.where_values.include?("name = 'David'") + assert !Developer.scoped.where_values.include?("name = 'Maiha'") end end diff --git a/activerecord/test/cases/relation_scoping_test.rb b/activerecord/test/cases/relation_scoping_test.rb index 30a783d5a225b..a91072b94b9f9 100644 --- a/activerecord/test/cases/relation_scoping_test.rb +++ b/activerecord/test/cases/relation_scoping_test.rb @@ -132,8 +132,6 @@ def test_scoped_create_with_create_with_has_higher_priority 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" @@ -141,7 +139,7 @@ def test_ensure_that_method_scoping_is_correctly_restored rescue end - assert_equal scoped_methods, Developer.send(:current_scoped_methods) + assert !Developer.scoped.where_values.include?("name = 'Jamis'") end end @@ -310,72 +308,173 @@ def test_default_scope assert_equal expected, received end - def test_default_scope_with_lambda - expected = Post.find_all_by_author_id(2) - PostForAuthor.selected_author = 2 - received = PostForAuthor.all - assert_equal expected, received - expected = Post.find_all_by_author_id(1) - PostForAuthor.selected_author = 1 - received = PostForAuthor.all + 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_scope_with_inheritance + wheres = InheritedPoorDeveloperCalledJamis.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 } assert_equal expected, received end - def test_default_scope_with_thing_that_responds_to_call - klass = Class.new(ActiveRecord::Base) do - self.table_name = 'posts' + def test_nested_scope + expected = Developer.find(:all, :order => 'salary DESC, 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 - klass.class_eval do - default_scope Class.new(Struct.new(:klass)) { - def call - klass.where(:author_id => 2) - end - }.new(self) + def test_scope_overwrites_default + expected = Developer.find(:all, :order => 'salary DESC, name DESC').collect { |dev| dev.name } + received = DeveloperOrderedBySalary.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 = DeveloperOrderedBySalary.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 = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do + DeveloperOrderedBySalary.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 = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary } + assert_equal expected, received + end - records = klass.all - assert_equal 3, records.length - assert_equal 2, records.first.author_id + 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 + + def test_default_scope_attribute + jamis = PoorDeveloperCalledJamis.new(:name => 'David') + assert_equal 50000, jamis.salary + end + + def test_where_attribute + aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron') + assert_equal 20, aaron.salary + assert_equal 'Aaron', aaron.name + end + + def test_where_attribute_merge + aaron = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') + assert_equal 'Aaron', aaron.name + end + + def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit + posts_limit_offset = Post.limit(3).offset(2) + posts_offset_limit = Post.offset(2).limit(3) + assert_equal posts_limit_offset, posts_offset_limit + end + + def test_create_with_merge + aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge( + PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new + assert_equal 20, aaron.salary + assert_equal 'Aaron', aaron.name + + aaron = PoorDeveloperCalledJamis.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 = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new + assert_equal 'Jamis', jamis.name + end +end + +class DeprecatedDefaultScopingTest < ActiveRecord::TestCase + fixtures :developers, :posts + + 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, DeveloperCalledDavid.count - assert_equal 11, DeveloperCalledDavid.unscoped.count + assert_equal 1, DeprecatedDeveloperCalledDavid.count + assert_equal 11, DeprecatedDeveloperCalledDavid.unscoped.count end def test_default_scope_is_unscoped_on_create - assert_nil DeveloperCalledJamis.unscoped.create!.name + 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, DeveloperCalledDavid.find(:all).map(&:id).sort - assert_equal nil, DeveloperCalledDavid.create!.name + 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, DeveloperCalledJamis.find(:all).map(&:id).sort - assert_equal 'Jamis', DeveloperCalledJamis.create!.name + 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_equal ['salary DESC'], DeveloperOrderedBySalary.scoped.order_values }.join + Thread.new { assert_equal ['salary DESC'], DeprecatedDeveloperOrderedBySalary.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 + klass = Class.new(DeprecatedDeveloperOrderedBySalary) + ActiveSupport::Deprecation.silence { 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 + assert_nil DeprecatedDeveloperOrderedBySalary.scoped.limit_value + assert_equal ['salary DESC'], DeprecatedDeveloperOrderedBySalary.scoped.order_values end def test_default_scope_called_twice_merges_conditions @@ -385,8 +484,10 @@ def test_default_scope_called_twice_merges_conditions Developer.create!(:name => "Brian", :salary => 100000) klass = Class.new(Developer) - klass.__send__ :default_scope, :conditions => { :name => "David" } - klass.__send__ :default_scope, :conditions => { :salary => 100000 } + 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 @@ -399,9 +500,11 @@ def test_default_scope_called_twice_in_different_place_merges_where_clause Developer.create!(:name => "Brian", :salary => 100000) klass = Class.new(Developer) - klass.class_eval do - default_scope where("name = 'David'") - default_scope where("salary = 100000") + ActiveSupport::Deprecation.silence do + klass.class_eval do + default_scope where("name = 'David'") + default_scope where("salary = 100000") + end end assert_equal 1, klass.count @@ -411,96 +514,90 @@ def test_default_scope_called_twice_in_different_place_merges_where_clause 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 } + 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 = DeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do - DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } + received = DeprecatedDeveloperOrderedBySalary.send(:with_scope, :find => { :order => 'name DESC'}) do + DeprecatedDeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary } 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 = DeveloperOrderedBySalary.by_name.find(:all).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 = DeveloperOrderedBySalary.reorder('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 = DeveloperOrderedBySalary.send(:with_exclusive_scope, :find => { :limit => 100 }) do - DeveloperOrderedBySalary.find(:all).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 = DeveloperOrderedBySalary.find(:all, :order => 'salary').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 = PostWithComment.scoped - assert_equal 2, posts.count + 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', PoorDeveloperCalledJamis.create!(:name => 'David').name - assert_equal 200000, PoorDeveloperCalledJamis.create!(:name => 'David', :salary => 200000).salary + 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, PoorDeveloperCalledJamis.create!(:salary => nil).salary - assert_equal 50000, PoorDeveloperCalledJamis.create!(:name => 'David').salary + assert_equal nil, DeprecatedPoorDeveloperCalledJamis.create!(:salary => nil).salary + assert_equal 50000, DeprecatedPoorDeveloperCalledJamis.create!(:name => 'David').salary end def test_default_scope_attribute - jamis = PoorDeveloperCalledJamis.new(:name => 'David') + jamis = DeprecatedPoorDeveloperCalledJamis.new(:name => 'David') assert_equal 50000, jamis.salary end def test_where_attribute - aaron = PoorDeveloperCalledJamis.where(:salary => 20).new(:name => 'Aaron') + 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 = PoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') + aaron = DeprecatedPoorDeveloperCalledJamis.where(:name => 'foo').new(:name => 'Aaron') assert_equal 'Aaron', aaron.name end - def test_scope_composed_by_limit_and_then_offset_is_equal_to_scope_composed_by_offset_and_then_limit - posts_limit_offset = Post.limit(3).offset(2) - posts_offset_limit = Post.offset(2).limit(3) - assert_equal posts_limit_offset, posts_offset_limit - end - def test_create_with_merge - aaron = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20).merge( - PoorDeveloperCalledJamis.create_with(:name => 'Aaron')).new + 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 = PoorDeveloperCalledJamis.create_with(:name => 'foo', :salary => 20). + 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 = PoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new + jamis = DeprecatedPoorDeveloperCalledJamis.create_with(:name => 'Aaron').create_with(nil).new assert_equal 'Jamis', jamis.name end end diff --git a/activerecord/test/models/bulb.rb b/activerecord/test/models/bulb.rb index 7178bb0d0072a..89ee5416bf72c 100644 --- a/activerecord/test/models/bulb.rb +++ b/activerecord/test/models/bulb.rb @@ -1,14 +1,15 @@ class Bulb < ActiveRecord::Base - - default_scope :conditions => {:name => 'defaulty' } + def self.default_scope + where :name => 'defaulty' + end belongs_to :car - attr_reader :scoped_methods_after_initialize + attr_reader :scope_after_initialize - after_initialize :record_scoped_methods_after_initialize - def record_scoped_methods_after_initialize - @scoped_methods_after_initialize = self.class.scoped_methods.dup + after_initialize :record_scope_after_initialize + def record_scope_after_initialize + @scope_after_initialize = self.class.scoped end end diff --git a/activerecord/test/models/car.rb b/activerecord/test/models/car.rb index e7db3d3423a28..a978debb58238 100644 --- a/activerecord/test/models/car.rb +++ b/activerecord/test/models/car.rb @@ -1,6 +1,7 @@ class Car < ActiveRecord::Base has_many :bulbs + has_many :foo_bulbs, :class_name => "Bulb", :conditions => { :name => 'foo' } has_many :tyres has_many :engines has_many :wheels, :as => :wheelable @@ -14,9 +15,13 @@ class Car < ActiveRecord::Base end class CoolCar < Car - default_scope :order => 'name desc' + def self.default_scope + order 'name desc' + end end class FastCar < Car - default_scope order('name desc') + def self.default_scope + order 'name desc' + end end diff --git a/activerecord/test/models/categorization.rb b/activerecord/test/models/categorization.rb index 09489b8ea4e7b..39441e86109ac 100644 --- a/activerecord/test/models/categorization.rb +++ b/activerecord/test/models/categorization.rb @@ -13,7 +13,9 @@ class Categorization < ActiveRecord::Base class SpecialCategorization < ActiveRecord::Base self.table_name = 'categorizations' - default_scope where(:special => true) + def self.default_scope + where(:special => true) + end belongs_to :author belongs_to :category diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb index 32d060cf09013..28b31caf7b1af 100644 --- a/activerecord/test/models/developer.rb +++ b/activerecord/test/models/developer.rb @@ -86,7 +86,11 @@ def raise_if_projects_empty! class DeveloperOrderedBySalary < ActiveRecord::Base self.table_name = 'developers' - default_scope :order => 'salary DESC' + + def self.default_scope + order('salary DESC') + end + scope :by_name, order('name DESC') def self.all_ordered_by_name @@ -98,15 +102,72 @@ def self.all_ordered_by_name class DeveloperCalledDavid < ActiveRecord::Base self.table_name = 'developers' - default_scope :conditions => "name = 'David'" + + def self.default_scope + where "name = 'David'" + end end class DeveloperCalledJamis < ActiveRecord::Base self.table_name = 'developers' - default_scope :conditions => { :name => 'Jamis' } + + def self.default_scope + where :name => 'Jamis' + end +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' - default_scope :conditions => { :name => 'Jamis', :salary => 50000 } + + def self.default_scope + where :name => 'Jamis', :salary => 50000 + end +end + +class InheritedPoorDeveloperCalledJamis < DeveloperCalledJamis + self.table_name = 'developers' + + def self.default_scope + super.where :salary => 50000 + end +end + +ActiveSupport::Deprecation.silence do + class DeprecatedDeveloperOrderedBySalary < ActiveRecord::Base + self.table_name = 'developers' + default_scope :order => 'salary DESC' + + 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 } + end end diff --git a/activerecord/test/models/pirate.rb b/activerecord/test/models/pirate.rb index 0d3f62bb339dd..5e0f5323e6cf6 100644 --- a/activerecord/test/models/pirate.rb +++ b/activerecord/test/models/pirate.rb @@ -34,7 +34,7 @@ class Pirate < ActiveRecord::Base :after_remove => proc {|p,b| p.ship_log << "after_removing_proc_bird_#{b.id}"} has_many :birds_with_reject_all_blank, :class_name => "Bird" - has_one :bulb, :foreign_key => :car_id + has_one :foo_bulb, :foreign_key => :car_id, :class_name => "Bulb", :conditions => { :name => 'foo' } accepts_nested_attributes_for :parrots, :birds, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } accepts_nested_attributes_for :ship, :allow_destroy => true, :reject_if => proc { |attributes| attributes.empty? } diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index 82894a3d57ff1..632f83e5d482d 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -142,20 +142,25 @@ class SubStiPost < StiPost self.table_name = Post.table_name end -class PostWithComment < ActiveRecord::Base - self.table_name = 'posts' - default_scope where("posts.comments_count > 0").order("posts.comments_count ASC") +ActiveSupport::Deprecation.silence do + class DeprecatedPostWithComment < ActiveRecord::Base + self.table_name = 'posts' + default_scope where("posts.comments_count > 0").order("posts.comments_count ASC") + end end class PostForAuthor < ActiveRecord::Base self.table_name = 'posts' cattr_accessor :selected_author - default_scope lambda { where(:author_id => PostForAuthor.selected_author) } end class FirstPost < ActiveRecord::Base self.table_name = 'posts' - default_scope where(:id => 1) + + def self.default_scope + where(:id => 1) + end + has_many :comments, :foreign_key => :post_id has_one :comment, :foreign_key => :post_id end diff --git a/activerecord/test/models/reference.rb b/activerecord/test/models/reference.rb index e33a0f2acc4f5..76c0a1a32ed98 100644 --- a/activerecord/test/models/reference.rb +++ b/activerecord/test/models/reference.rb @@ -18,6 +18,9 @@ def make_comments end class BadReference < ActiveRecord::Base - self.table_name ='references' - default_scope :conditions => {:favourite => false } + self.table_name = 'references' + + def self.default_scope + where :favourite => false + end end diff --git a/activerecord/test/models/without_table.rb b/activerecord/test/models/without_table.rb index 87f80911e1adb..1a63d6ceb6a48 100644 --- a/activerecord/test/models/without_table.rb +++ b/activerecord/test/models/without_table.rb @@ -1,3 +1,5 @@ class WithoutTable < ActiveRecord::Base - default_scope where(:published => true) -end \ No newline at end of file + def self.default_scope + where(:published => true) + end +end