ruby 使用`tap`构建Rails作用域

r1zhe5dt  于 2023-06-05  发布在  Ruby
关注(0)|答案(3)|浏览(127)

我有一个方法

class Student < ActiveRecord::Base
  def self.search(options = {})
    all.tap do |s|          
      s.where(first_name: options[:query])     if options[:query]
      s.where(graduated:  options[:graduated]) if options[:graduated]

      # etc there are many more things that can be filtered on...
    end
  end
end

当调用这个方法时,我得到的是所有的结果,而不是我期望的过滤集。似乎我的tap功能没有像我期望的那样工作。正确的方法是什么(不将all赋值给变量)?如果可能的话,我想在这里使用块)。

nxagd54h

nxagd54h1#

tap对此不起作用。

  • all是一个ActiveRecord::Relation,一个等待发生的查询。
  • all.where(...)返回 newActiveRecord::Relation新查询。
  • 然而,检查tap的文档,您会看到它返回调用它的对象(在本例中为all),而不是块的返回值。

也就是说,它是这样定义的:

def tap
  yield self # return from block **discarded**
  self
end

当你想要的只是:

def apply
  yield self # return from block **returned**
end

或者类似的东西。
这就是为什么你总是得到 * 所有 * 返回的对象,而不是查询得到的对象。我的建议是建立发送给where的散列,而不是链接where调用。就像这样:

query = {}
query[:first_name] = options[:query]     if options[:query]
query[:graduated]  = options[:graduated] if options[:graduated]
# ... etc.

all.where(query)

或者一个更好的实现:

all.where({
  first_name: options[:query],
  graduated:  options[:graduated],
}.delete_if { |_, v| v.empty? })

(If中间变量不符合您的口味。)

fivyi3re

fivyi3re2#

您可以轻松创建let函数:

class Object
  def let
    return yield self
  end
end

然后这样使用:

all.let do |s|          
  s=s.where(first_name: options[:query])     if options[:query]
  s=s.where(graduated:  options[:graduated]) if options[:graduated]

  # etc there are many more things that can be filtered on...

  s
end

taplet的区别在于tap返回对象,而let返回块的返回值。

4ngedf3f

4ngedf3f3#

现在(ruby >= 2.5)你可以使用Object.yield_self

def self.search(options = {})
    all.yield_self do |s|          
      s = s.where(first_name: options[:query])     if options[:query]
      s = s.where(graduated:  options[:graduated]) if options[:graduated]
      s
    end
  end

相关问题