Rails ActiveRecord的default_scope的坑
其实,我是特别反对使用default_scope
的。它很强大,同时也非常的难于驾驭。它的优点在于我们只需要在Model
层增加一行代码,就能解决整个项目中的如何一个地方数据的获取。
举个例子,我们有一个项目的Model,前期比较简单,我们在代码的如何地方都可以方便的使用Project.find
或者 Project.where...
。然后,我们来了一个新的需求,需要给Project
增加一个开关,只有通过审核的项目才能在项目中显示。这个时候,我们首先想到的方案就是使用 default_scope
。 So easy! 我们只需要在Model中增加一行代码 default_scope { where("checked is not null") }
。 表面上,或者没有经过全面的测试,这行代码没有问题。而且非常方便的解决了我们的问题。但其中隐藏很多问题。
问题
会在系统的其他引用Project
的地方报 nil
异常
比如我们有一个User
模型, 它跟 Project
的关系是 belongs_to
的关系。所以,下面的代码
@users.each do |user|
user.project.name
end
是比较常见的case。 但由于project
的default_scope
的原因,导致 project
为nil
, 这个时候,就会报错~
解决办法:每一个引用project
的地方,都做nil
判断。 这个解决方案不好,特别麻烦。一种比较好的方案是引入 gem 'unscoped_associations'
.
组合而成的SQL
会有问题~
这个问题比较隐晦,很难发现。我先用一个例子说明,接着上面说的。
1. 有一个新的模型 Investor
,它跟Project
一样,有同样的default_scope
的条件,也就是说在 investors
表 和 projects
表,都有一个checked
字段。
2. 约会的模型 Interview
会关联上 Project
和 Investor
现在有一个需求,客户需要获取所有的Interview
。我们一般会写如下的代码
Investor.includes(:project, :investor).all
上面这行代码会报错!原因是框架在组合SQL的时候,会有两个checked
字段的条件,如果我们使用default_scope { where("checked is not null") }
这种方式,框架是不能设置 projects.checked
这样的条件(如果使用Hash方式,是可以智能的组合的,所以,尽量要使用hash).
解决办法:通过arel 底层组合SQL
default_scope { where(arel_table[:checked].not_eq(nil)) }
总结
default_scope
给我们解决的问题的同时,会引入更多的问题。甚至需要修改底层代码才能解决。所以,在使用default_scope
的时候,一定要清楚自己做什么。