2014-01-22 72 views
3

我有一個看起來像建立一個Rails範圍使用`tap`

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分配給一個變量,如果可能,我想在這裏使用塊)。

回答

3

tap對此不起作用。

  • allActiveRecord::Relation,查詢等待發生。
  • all.where(...)返回一個新的ActiveRecord::Relation新的查詢。
  • 但是檢查documentation的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? }) 

(如果中間變量是不合你的口味)

+0

感謝您的真棒深入解釋! –

5

您可以輕鬆創建一個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返回塊返回值。

+0

我也喜歡不猴補丁像'Object' 。不應該調用's.where(...)'修改正在調用該塊的對象? –

+0

不 - 因爲's.where'本身不會修改's'!這是一個純粹的函數方法,它返回一個新的查詢對象而不修改現有的查詢對象。 –

+0

我明白了。這就說得通了。謝謝澄清! –