使用`tap`构建Rails范围

Kyl*_*cot 4 ruby ruby-on-rails ruby-on-rails-4 rails-activerecord

我的方法看起来像

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
Run Code Online (Sandbox Code Playgroud)

在调用此方法时,我正在收回所有结果,而不是我期望的过滤集.好像我的tap功能没有像我期望的那样工作.这样做的正确方法是什么(没有赋值all给变量.如果可能,我想在这里使用块).

Ida*_*rye 5

您可以轻松创建一个let功能:

class Object
  def let
    return yield self
  end
end
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

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
Run Code Online (Sandbox Code Playgroud)

tap和之间的区别lettap返回对象并let返回块返回值.


amn*_*mnn 5

tap不会为此工作。

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

    即它的定义如下:

    def tap
      yield self # return from block **discarded**
      self
    end
    
    Run Code Online (Sandbox Code Playgroud)

    当你想要的只是:

    def apply
      yield self # return from block **returned**
    end
    
    Run Code Online (Sandbox Code Playgroud)

    或者类似的东西。

这就是为什么您不断获取返回的所有对象,而不是查询结果的对象。我的建议是,您构建发送到的哈希值where,而不是链接where调用。就像这样:

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

all.where(query)
Run Code Online (Sandbox Code Playgroud)

或者一个可能更好的实现:

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

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