msn email google-talk twitter tumblr flickr

override the default_scope

default_scope是rails 2.3加入的功能.使用起来很方便,特别适合解决一些遗留项目的功能修改,到了Rails3官方还是提供了一个unscoped方法供大家取消default_scope. 具体使用方法请看这里unscoped.

那么在rails 2.3的项目中如何override default_scope?

项目中就遇到这个问题.很简单的需求,为一些对象提供伪删除功能.开发很常见的功能,如果真删除会导致一些关联对象失效,失去可用性;所以通常使用标志位,或者干脆使用删除时间戳作为标志位,查询时用标志位来判断对象是否已被删除. 但如果是遗留的或者多人协作的项目,出问题的风险会很大,因为我们不清楚查询被埋在了哪些地方,每处添加难免有遗漏;业务逻辑也各有不同,有些地方也许就需要查询所有对象或查询已经删除的对象,改起来是非常之危险. 这个时候default_scope就能帮上大忙了,一下子给所有的查询都加上了判断条件,我们只需要让业务逻辑特殊的地方不使用default_scope就可以了.

思路是这样,就可以实施了,先来详细了解下Default Scoping.相信很多童鞋都看过What’s New in Edge Rails: Default Scoping这篇吧?

里面有这样一段话:

There are some things to keep in mind, however. First is that scopes like :join, :offset, :limit and :order will get clobbered by the innermost rule. For example, here the default scope ordering loses out to the named_scope ordering.

意思是当default_scope和named_scope中都定义了:join,:offset,:limit:order条件的话,default_scope的条件是会被named_scope的同名条件覆盖的.

   1      class Article < ActiveRecord::Base
   2        default_scope :order => 'created_at DESC'
   3        named_scope :published, :conditions => { :published => true },
   4                                :order => 'published_at DESC'
   5      end
   6  
   7      # published_at DESC clobbers default scope
   8      Article.published
   9      #=> "SELECT * FROM `articles` WHERE published = true ORDER BY published_at DESC"

可以看到代码行为的确如文章所说,default_scope的:order被覆盖了.

换个角度理解这段话,意思是我们如果在default_scope中用:conditions条件,named_scope是不会覆盖同名条件参数的值的.经过我的实践,无论是同一查询链中其他的:conditions条件怎样default_scope的:conditions是一定生效的.

那现在终于又回到我们最先的问题:要怎么关闭default_scope或者覆盖default_scope的:conditions条件呢?

还是刚才那篇文章中说道:

And for those occasions when you want to override or remove your default scope, just use with_exclusive_scope:

   1      class Article < ActiveRecord::Base
   2        default_scope :order => 'created_at DESC'
   3      end
   4  
   5      # Ignore other scoping within this block
   6      Article.with_exclusive_scope { find(:all) }  #=> "SELECT * FROM `articles`

问题似乎解决了,其实不然;使用with_exclusive_scope方法没错,很不幸的,如果只是按照这个方法去调用你会得到一个异常报告:

    NoMethodError in foobar
    protected method 'with_exclusive_scope' called for #<Class:0x2145f6c>

受保护的方法被调用…汗,静下心来再想想,既然是要调用受保护的方法,我们绕过检查器去调用就行了,使用元编程的小技巧:

   1      Foobar.send(:with_exclusive_scope) do    
   2          #your logic here
   3      end

经过我测试通过,看来是必须要用上面这个方法来调用才能关闭或者覆盖default_scope的:conditions条件的,虽然这样做有些另类和非常规,但是偶尔在特殊情况下使用也是不错的选择.

感慨Ruby的神奇,受保护的方法也能调用.