Rails 如何让 scope 可以携带参数

kevinhua · 2012年03月30日 · 最后由 mamulian 回复于 2015年01月14日 · 9673 次阅读

scope :type_with_arrays(arr), where(:type.in => arr).desc("created_at")

但这样不能传递参数,如何才能让 scope 携带参数?

lambda 或者用方法代替

看文档……

#1 楼 @hisea 有没有再详细一点的?

#2 楼 @iwinux 社区的作用就是帮人减少阅读文档的时间,如果都是看文档,哪还有社区的必要吗?

#4 楼 @kevinhua 社区是在你翻了文档,认真思考后,没有找到解决方法,再向大家请教的地方,不然我只能回一句: RTFM

自己来贴答案吧:

scope :admin, (lambda do |company_id| 
  {:conditions => ['company_id = ?', company_id]}
end)

再来一个更全面的参考:

class User  
  scope :by_age, lambda do |age|  
    joins(:profile).where('profile.age = ?', age) unless age.nil?  
  end  
  scope :by_name, lambda{ |name| where(name: name) unless name.nil? }  
  scope :by_email, lambda do |email|  
    joins(:profile).where('profile.email = ?', email) unless email.nil?  
  end  
end  

User.by_age(33).by_name(params[:name]).by_email(params[:email]).all

我是一直

scope :by_level, Proc.new { |level| where(:level => level) }

api 文档里是

scope :colored, lambda { |color| where(:color => color) }

如果是 ruby 1.9 就可以

scope :colored, ->(color) { where(:color => color) }

不过这个写法好怪。。。很难记忆

#5 楼 @foxzool 关键是一种态度,不回答没问题,不强求每个人都来回答。不回答也就罢了,冷冰冰的来一句“看文档……”,谁不知道看文档。就跟学生问老师题目,老师也来一句“看课本”,“看资料书”。

最后的实现办法:

scope :type_with_arrays, lambda { |arr| where(:_type.in => arr.map {|a| a.capitalize} ).desc("created_at") unless arr.empty? }

有点繁琐,但能用就管啦。

#6 楼 @kevinhua 改用 -> 吧,跨行的话用 lambda 总是会忘记那个 () ,导致莫名其妙的提示一个语法错误,重点是发现不了是这里丢了 (),-> 还能设置默认值

scope :admin, ->(company_id){ where(:company_id => company_id)}

#4 楼 @kevinhua 说实话,看了这句 "社区的作用就是帮人减少阅读文档的时间,如果都是看文档,哪还有社区的必要吗?" 感觉很心寒。

#10 楼 @kevinhua 关键是一种态度,不回答没问题,但是对于回答你问题的人的这个态度,你是在鼓励大家以后都直接无视你的帖子嘛?

#3 楼 @kevinhua

请看这个文档 http://apidock.com/rails/ActiveRecord/Scoping/Named/ClassMethods/scope

注意其中一段: class Shirt < ActiveRecord::Base scope :red, where(:color => 'red') end

The above calls to scope define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red, in effect, represents the query Shirt.where(:color => 'red').

所以我一般都没有参数用 scope,有参数直接定义方法,也可可以传入多个参数,结构上也更容易读

def self.color(c) where(:color => c) end

两者等价,选择哪个看个人喜好了。

#13 楼 @lgn21st 也要理解提问者的心寒。

其实 2 楼的回答跟不回答没什么两样,纯粹是浪费社区资源,你叫别人看文档至少也得给个范围什么的

scope 在我体会中,一些简单的查询可以用它,加上 chained,还可以。 复杂的查询还是上 def method 吧。

#16 楼 @IchiNiNiIchi #15 楼 @kevinhua 到底看过文档了么?先看的文档还是先到这里来提问? 如果是先看过文档了,那么到底文档什么地方不明白? 或者是不知道文档在那里?

如果你的问题中包含以上信息,我相信你不会被人说RTFM

想问下 scope 使用 Proc 实现,也可以用普通方法实现是吧?这种所谓的匿名函数有什么好处呢?感觉就像一个有静态变量的函数,也就是保存着状态的函数。

#19 楼 @lgn21st 躺枪了。因为 LZ 的帖子是发在 ruby 节点的,然后我用“ruby scope"搜了一下,发现首页基本上找不到有跟 scope 方法相关的信息,因此以为是高级的内容。LZ 坑人啊,关于 Rails 的问题发在 ruby 节点

#21 楼 @IchiNiNiIchi 真不好意思,我已经把节点信息修改了。

#20 楼 @jiffies Scope并不是一个新东西,在 Rails2 时代就引入了,的确用 class_method 就可以实现 scope 的方法,Scope 的好处是返回一个 ActiveRecord::Relation ,然后你可以在返回对象上继续执行其他的 scope 操作,类似 category.posts.published

With these scopes, you can use every method previously covered such as where, joins and includes. All scope methods will return an ActiveRecord::Relation object which will allow for further methods (such as other scopes) to be called on it.



这里的 scope 上用的 Proc,我觉得不应当作匿名函数来理解,而应该理解成 Procedure。

Proc 用意是传入参数后,只有在调用 scope 的时候在调用的 context 中去对 Proc 进行求值 (eval),而不是在定义 Scope 的 context 下求值。因为 Scope 方法在定义后即被 Cache 了,所以任何传入的变量都需要通过 Proc 在调用时传入。

如果是用普通方法去实现,的确不需要用 Proc,但是也失去了通过 Proc 获得的灵活性,这就是 Ruby 语言灵活性所在呀。

#22 楼 @lgn21st 愈发觉得 ruby 博大精深了,继续学习

@lgn21st 请教问题,如你在#22 楼所说, " 如果是用普通方法去实现,的确不需要用 Proc,但是也失去了通过 Proc 获得的灵活性,这就是 Ruby 语言灵活性所在呀。" 我用 self.method 也可以级联阿。为啥用这个呢?

#24 楼 @xds2000 这个问题太赞了,非常值得深思,研究并理解 AR 的 Scope 的设计理念。 我试着在没有深入研究的基础上,借花献佛用 José Valim 的话来回复你吧

You can chain scopes, but they will be evaluated at the moment you call them. That said, when you call started, it will execute the lambda, so it will have a frozen Time.now. In other words, chaining lambda scopes will likely give you the wrong result.

因为要 chain scopes,并 lazy evaluation,所以不能每次 dot 调用就立即执行并返回结果,所以借助 Proc 来封装一个计算的过程,直到最后需要的时候在执行,如果自行用self.method级联的话,每次方法调用都会求值一次,没有 Lazy evaluation 的性能优势。

因为需要每次 dot 方法调用都返回 ActiveRecord::Relation 所以抽取这个模式成为通用模式,于是就变成了 named_scope,也就是后来的 scope 方法,用 scope 你不必自己管理方法的 return 结果。

上面两个“因为”是我对 Scoped 和 Proc 的灵活性的理解,还请有过研究的高人指正,呵呵。

#3 楼 @kevinhua

我看到这个问题之后,打开 api.rubyonrails.org,搜索 scope,马上看到答案了,但由于那个页面是框架包着的,复制不到那个页面链接,所以就直接回了一句“看文档”,因为 Rails 只有两份文档:http://api.rubyonrails.org/http://guides.rubyonrails.org/,一个有目录,一个有搜索框,找起来并不难,这样的回答,不算偷懒吧?

#25 楼 @lgn21st 我翻了下 scope 方法的源码,我的理解是它把传入的 proc 定义成 model 的一个 class method 了?

# File activerecord/lib/active_record/scoping/named.rb, line 174
def scope(name, scope_options = {})
  # 略去部分代码
  scope_proc = lambda do |*args|
    options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options
    # 略去部分代码
  end
  singleton_class.send(:redefine_method, name, &scope_proc) # 注意 redefine_method 的使用
end

#1 楼 @hisea #2 楼 @iwinux #5 楼 @foxzool #12 楼 @vkill #16 楼 @IchiNiNiIchi #23 楼 @jiffies #24 楼 @xds2000 #25 楼 @lgn21st

感谢大家热烈讨论,首先我对我的过激言辞抱歉。

社区是一个多样性十足的地方,混社区的除了高手之外,更多的是新手。他们可能并不熟悉如何去看文档,在哪看文档,在文档里查找什么样的信息;看了文档也不一定清楚怎样用于实践。对于他们来说,有时一个 Rails 环境的设置,都会花费很多的时间。

所以新手们的提问,在高手看来不是什么问题,甚至不懈回答。但是也需要理解他们的需求,你的点滴指点,对于他们来说,就是成长为高手的支持和鼓励。

即使一个简单的问题,经过合适的指引和讨论,也可以成为精品。为后来者提供有用的信息,也为社区提供源源不断的内容。

#17 楼 @xds2000  写 class method +1 也可以 chaine 也可以带参数

#26 楼 @iwinux 这就是我在 22 楼说两者等价,scoped 到最后还是定义 class_method.

在 ARel 下 scope 已经变成语法糖了。对我来说,语法糖就是简单易用,提高可读性,如果需要多个参数,包含逻辑,可读性下降了还是定义 class method.

#25 楼 @lgn21st 刚刚有试过,在调用 class method 和 lambda 的时候都会返回一个 ActiveRecord::Relation,而 ActiveRecord::Relation 本身就是 lazy loaded,所以感觉上两者应该基本上是一样的;

唯一一个我可以想到的不同是在 1.8 下面,lambda 还不可以有 default value,而 class method 可以定义;

#25 楼 @lgn21st 这段有深度啊,我一般平时使用时,如果只在一处调用的话,直接在 controller 里面写 where 查询,如果会重用的话,则会在 model 定义方法或者 scop,那么究竟是定义方法还是是 scope 呢?一直没有思考过,这段让我茅塞顿开啊,想了一下,一般有参数的情况下,都是写一个 self.mothod,没有考虑过 lazy load 的问题,级联调用的话,由于 ruby 的方法总是会有返回值的,这个已经习惯了。

Mark 一下,快忘了。

scope :state_in, ->(states) { where('xxxs.state in (?)', states) if states } 这样不就得了么。。。我不知道为什么还要 lambda 查询 xxxs 的状态。参数就是 states!

需要 登录 后方可回复, 如果你还没有账号请 注册新账号