2017-04-10 52 views
-1

我使用的是Rails,但這裏的基本問題更廣泛地適用。我在我的web應用上有一個報告頁面,允許用戶指定他們過濾的內容,並根據這些過濾器(MongoDB)查詢數據庫。清理凌亂的代碼,基於多個選項的查詢

的數據是基於圍繞旅館,用戶必須首先選擇的酒店(state_onestate_twostate_three),則的酒店(planningunder_constructionoperational),狀態然後任選的標準的區域,價格範圍(200,300400)。用戶可以選擇多個這些選項。

我現在這樣做的方法是創建一個空數組,迭代每個區域,並在用戶選擇該區域時將該區域推入數組。然後,我遍歷THAT數組,並評估這些區域中的酒店的狀態,如果任何酒店具有用戶選擇的狀態,那麼我將該酒店添加到新的空白數組中。然後我爲價格範圍做同樣的事情。

這工作,但代碼是進攻凌亂,這裏的代碼示例:

def find_hotel 
    hotels = find_all_hotels 
    first_array = [] 
    hotels.each do |hotel| 
    if params[:options][:region].include? 'state_one' and hotel.state == :one 
     first_array.push(hotel) 
    elsif params[:options][:region].include? 'state_two' and hotel.state == :two 
     first_array.push(hotel) 
    elsif params[:options][:region].include? 'state_three' and hotel.state == :three 
     first_array.push(hotel) 
    end 
    end 

    second_array = [] 
    first_array.each do |hotel| 
    if params[:options][:region].include? 'planning' and hotel.status == :planning 
     first_array.push(hotel) 
    elsif params[:options][:region].include? 'under_construction' and hotel.status == :under_construction 
     first_array.push(hotel) 
    elsif params[:options][:region].include? 'operational' and hotel.status == :operational 
     first_array.push(hotel) 
    end 
    end 

    third_array = [] 
    second_array.each do |hotel| 
    # More of the same here, this could go on forever 
    end 
end 

有哪些更好的實現這一目標的方法嗎?

+1

爲什麼你有'hotel_is_in_state_one'而不是像'hotel_state'這樣的東西是':one'? – tadman

+1

ActiveRecord的'where'方法將數組轉換爲SQL'WHERE/IN'語句。所以你應該可以做一些像'Hotel.where(state:params [:options] [:region],region:params [:options] [:region])''。當然,你會想通過一個allowed_pa​​rams機制來過濾你的params來防止攻擊。 – moveson

+0

@tadman我爲這個例子編寫了代碼,它不是我的實際代碼,只是我認爲會說明我想要它做什麼,我的實際應用程序是不同的,但我不能在這裏使用它,因爲它並不全是我的。 – Justin

回答

1

如何:

STATES = [:one, :two, :three] 
STATUSES = [:planning, :under_construction, :operational] 
PRICES = [200, 300, 400] 

def find_hotel 
    region = params[:options][:region] 

    first_array = set_array(region, find_all_hotels, STATES, :state) 
    second_array = set_array(region, first_array, STATUSES, :status) 
    third_array = set_array(region, second_array, PRICES, :price_range) 
end 

def set_array(region, array, options, attribute) 
    array.each_with_object([]) do |element, result| 
    options.each do |option| 
     result << element if region.include?(option) && element[attribute] == option 
    end 
    end 
end 

UPDATE

新增attribute參數set_array,以使代碼工作,更新後的例子。

+0

這是一個很好的形式,但由於這些數組從不改變,所以它們應該是在課堂上聲明的常量值。例如'OPTIONS =%w [option_1 option_2 option 3]'。 – tadman

+0

嘿@Gerry,很抱歉,我對這一切都很陌生,不知道'each_with_object'是如何工作的。我現在正在讀它,但也許你可以用雷曼術語很快地在這裏向我解釋它?此外,我希望'find_hotel'能夠返回符合搜索條件的酒店數組或哈希值,我將如何擴展您的示例以獲得這樣的結果? – Justin

+0

@Justin'each_with_object'循環數組並返回一個對象,該對象的類型被定義爲一個參數。在這種情況下,它返回一個名爲'result'的'Array'('each_with_object([])')。和先創建一個數組並在一個普通的'each'循環中使用它(就像你的例子)是一樣的結果。檢查這裏的更多信息:http://stackoverflow.com/questions/19064209/how-is-each-with-object-supposed-to-work – Gerry

0

由於second_array是空的,無論您通過遍歷它(也許third_array)獲得也將是空的。

def find_hotel 
    hotels = find_all_hotels 

    first_array = hotels 
    .select{|hotel| params[:options][:region].include?("state_#{hotel.state}")} 

    first_array += first_array 
    .select{|hotel| params[:options][:region].include?(hotel.status.to_s)} 

    second_array = third_array = [] 

    ... 
end