2017-04-10 56 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