public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Added default_scope to Base [#1381 state:committed] (Paweł Kondzior)
lifo (author)
Sun Nov 16 10:06:41 -0800 2008
commit  2530d0eea8eaecd2c61f99225f050ff47973e9a0
tree    e32d4920a21417e79584c6c7a563a2cb948b4532
parent  d9f460a39b73fd2cf0f17f523cc4810d0bf44cac
...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
0
@@ -1,3 +1,19 @@
0
+*2.3.0/3.0*
0
+
0
+* Added default_scope to Base #1381 [PaweÅ‚ Kondzior]. Example:
0
+
0
+    class Person < ActiveRecord::Base
0
+      default_scope :order => 'last_name, first_name'
0
+    end
0
+
0
+    class Company < ActiveRecord::Base
0
+      has_many :people
0
+    end
0
+
0
+    Person.all             # => Person.find(:all, :order => 'last_name, first_name')
0
+    Company.find(1).people # => Person.find(:all, :order => 'last_name, first_name', :conditions => { :company_id => 1 })
0
+
0
+
0
 *2.2.1 [RC2] (November 14th, 2008)*
0
 
0
 * Ensure indices don't flip order in schema.rb #1266 [Jordi Bunster]
...
495
496
497
 
 
 
 
498
499
500
...
2016
2017
2018
 
 
 
 
 
 
 
 
 
 
2019
2020
2021
...
2031
2032
2033
2034
 
2035
2036
2037
...
495
496
497
498
499
500
501
502
503
504
...
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
...
2045
2046
2047
 
2048
2049
2050
2051
0
@@ -495,6 +495,10 @@ module ActiveRecord #:nodoc:
0
     superclass_delegating_accessor :store_full_sti_class
0
     self.store_full_sti_class = false
0
 
0
+    # Stores the default scope for the class
0
+    class_inheritable_accessor :default_scoping, :instance_writer => false
0
+    self.default_scoping = []
0
+
0
     class << self # Class methods
0
       # Find operates with four different retrieval approaches:
0
       #
0
@@ -2016,6 +2020,16 @@ module ActiveRecord #:nodoc:
0
           @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
0
         end
0
 
0
+        # Sets the default options for the model. The format of the
0
+        # <tt>method_scoping</tt> argument is the same as in with_scope.
0
+        #
0
+        #   class Person < ActiveRecord::Base
0
+        #     default_scope :find => { :order => 'last_name, first_name' }
0
+        #   end
0
+        def default_scope(options = {})
0
+          self.default_scoping << { :find => options, :create => options.is_a?(Hash) ?  options[:conditions] : {} }
0
+        end
0
+
0
         # Test whether the given method and optional key are scoped.
0
         def scoped?(method, key = nil) #:nodoc:
0
           if current_scoped_methods && (scope = current_scoped_methods[method])
0
@@ -2031,7 +2045,7 @@ module ActiveRecord #:nodoc:
0
         end
0
 
0
         def scoped_methods #:nodoc:
0
-          Thread.current[:"#{self}_scoped_methods"] ||= []
0
+          Thread.current[:"#{self}_scoped_methods"] ||= self.default_scoping
0
         end
0
 
0
         def current_scoped_methods #:nodoc:
...
522
523
524
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
526
527
...
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
0
@@ -522,6 +522,67 @@ class HasAndBelongsToManyScopingTest< ActiveRecord::TestCase
0
 end
0
 
0
 
0
+class DefaultScopingTest < ActiveRecord::TestCase
0
+  fixtures :developers
0
+
0
+  def test_default_scope
0
+    expected = Developer.find(:all, :order => 'salary DESC').collect { |dev| dev.salary }
0
+    received = DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
0
+    assert_equal expected, received
0
+  end
0
+
0
+  def test_default_scoping_with_threads
0
+    scope = [{:create=>nil, :find=>{:order=>"salary DESC"}}]
0
+
0
+    2.times do
0
+      Thread.new { assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods) }.join
0
+    end
0
+  end
0
+
0
+  def test_default_scoping_with_inheritance
0
+    scope = [{:create=>nil, :find=>{:order=>"salary DESC"}}]
0
+
0
+    # Inherit a class having a default scope and define a new default scope
0
+    klass = Class.new(DeveloperOrderedBySalary)
0
+    klass.send :default_scope, {}
0
+
0
+    # Scopes added on children should append to parent scope
0
+    expected_klass_scope = [{:create=>nil, :find=>{:order=>"salary DESC"}}, {:create=>nil, :find=>{}}]
0
+    assert_equal expected_klass_scope, klass.send(:scoped_methods)
0
+    
0
+    # Parent should still have the original scope
0
+    assert_equal scope, DeveloperOrderedBySalary.send(:scoped_methods)
0
+  end
0
+
0
+  def test_method_scope
0
+    expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
0
+    received = DeveloperOrderedBySalary.all_ordered_by_name.collect { |dev| dev.salary }
0
+    assert_equal expected, received
0
+  end
0
+
0
+  def test_nested_scope
0
+    expected = Developer.find(:all, :order => 'name DESC').collect { |dev| dev.salary }
0
+    received = DeveloperOrderedBySalary.with_scope(:find => { :order => 'name DESC'}) do
0
+      DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
0
+    end
0
+    assert_equal expected, received
0
+  end
0
+
0
+  def test_nested_exclusive_scope
0
+    expected = Developer.find(:all, :limit => 100).collect { |dev| dev.salary }
0
+    received = DeveloperOrderedBySalary.with_exclusive_scope(:find => { :limit => 100 }) do
0
+      DeveloperOrderedBySalary.find(:all).collect { |dev| dev.salary }
0
+    end
0
+    assert_equal expected, received
0
+  end
0
+
0
+  def test_overwriting_default_scope
0
+    expected = Developer.find(:all, :order => 'salary').collect { |dev| dev.salary }
0
+    received = DeveloperOrderedBySalary.find(:all, :order => 'salary').collect { |dev| dev.salary }
0
+    assert_equal expected, received
0
+  end
0
+end
0
+
0
 =begin
0
 # We disabled the scoping for has_one and belongs_to as we can't think of a proper use case
0
 
...
77
78
79
 
 
 
 
 
 
 
 
 
 
 
 
...
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
0
@@ -77,3 +77,15 @@ class DeveloperWithBeforeDestroyRaise < ActiveRecord::Base
0
     raise if projects.empty?
0
   end
0
 end
0
+
0
+class DeveloperOrderedBySalary < ActiveRecord::Base
0
+    self.table_name = 'developers'
0
+    default_scope :order => "salary DESC"
0
+
0
+    def self.all_ordered_by_name
0
+      with_scope(:find => { :order => "name DESC" }) do
0
+        find(:all)
0
+      end
0
+    end
0
+
0
+end

Comments

zargony Wed Nov 19 02:33:00 -0800 2008

Thanks for adding this; it’ll make things easier in many cases (even though I learned to live without it since I played around with default scopes )

peanut Fri Nov 21 09:29:44 -0800 2008

Thank you very much for this new feature. It will be very helpful for me.

odadata Mon Nov 24 07:42:00 -0800 2008

Awesome, finally it’s in!

Thank you guys, I owe you a beer.