在Rails 4中进行多对多的高级搜索

时间:2019-04-19 15:57:57

标签: ruby-on-rails ruby-on-rails-4 activerecord rails-activerecord ruby-on-rails-4.2

我有一个房地产清单,每个房地产都有一些功能。然后,我有2张桌子:

  • real_estates,用于存储用户添加的所有房地产。
  • re_home_features,用于存储管理员添加的所有默认功能,例如游泳池,壁橱,办公室,花园以及房地产可以拥有的许多功能

同一个房地产可以拥有许多功能,同一个房地产可以拥有许多不动产。我创建了这些模型:

real_estate.rb

class RealEstate < ActiveRecord::Base
    has_many :real_estate_home_features
    has_many :re_home_features, as: :home_features, through: :real_estate_home_features, dependent: :destroy
end

re_home_features.rb

class ReHomeFeature < ActiveRecord::Base
    has_many :real_estate_home_features
    has_many :real_estates, through: :real_estate_home_features
end

real_estate_home_feature.rb

class RealEstateHomeFeature < ActiveRecord::Base
    belongs_to :real_estate
    belongs_to :re_home_feature
end

有了这个,多对多关系就可以正常工作了。

我正在搜索具有某些参数的房地产,例如:

  • 客厅数
  • 浴室数量
  • 卖价(最小到最大)
  • 总面积(最小到最大)
  • 房地产代码
  • 还有许多其他参数

我的搜索是这样的:

real_estates_controller.rb

def search
    r = real_estates
    r = r.where(code: params['code']) if params['code'].present?
    r = r.where(city_name: params['city']) if params['city'].present?
    r = r.where(garage: params['garage']) if params['garage'].present?
    r = r.where(restrooms: params['restrooms']) if params['restrooms'].present?

    r = r.paginate(:page => params[:page], :per_page => 10)

    r
end

此搜索也可以正常工作。这样做没有问题,因为所有参数都在同一表的real_estates中。

但是现在,搜索有点复杂。我必须搜索具有特定功能的房地产。示例:我想要所有房地产,该房地产有4个洗手间,车库中有2辆汽车,并且有游泳池

在我的示例中,搜索有4个洗手间和2辆汽车的房地产返回了50个房地产,但是这些房地产中只有15个有游泳池。

如何过滤这50个房地产,以仅显示与“合并功能”相关的记录?

我无法在视图中进行验证,因为每页导致错误的编号。我认为筛选器必须在数据库查询语句中位于分页符之前。

感谢您的帮助!

环境

rails -v: Rails 4.2.1
ruby -v: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux]
so: Ubuntu 16.04.5 LTS
localhost database: MySQL (development)
server database: Postgres (production)

编辑1

基于@jvillian答案,我将更改控制器,在查询中添加一个JOIN。

if params['home_features'].present? && params['home_features'].any?
    r = r.joins(:re_home_features).where(re_home_features: {id: params['home_features']}).distinct(:id)
end

在我的测试中,我有:

params['home_features'] = [1 , 2, 3]

我有1个具有这3个功能的房地产。但是,在视图中,我获得了同一网站的3次展示。如果添加区别,则房地产仅显示一次。但是...如果我将参数更改为:

params['home_features'] = [1 , 2, 3, 500]

我没有具有这四个功能的房地产,但是结果是相同的。 没有区别,房地产显示3次。随着dinstict,房地产显示一次。预期结果为零结果,因为我希望房地产具有所有选定的功能。

但是我认为我们快到了!我将提供有关我的模型的一些信息:

表房地产

id | title      | description | code | city_name      | garage | ...
 7 | Your House | Awesome...  | 1234 | Rio de Janeiro | 4

表re_home_features

id | name
 1 | Pool
 2 | Garden
 3 | Tenis court
 4 | Closet

表real_estate_home_features-多对多关联

id | real_estate_id | re_home_feature_id
 1 |              7 |                  1
 2 |              7 |                  2
 3 |              7 |                  3

如果我跑步:

r = r.joins(:re_home_features).where(re_home_features: {id: [1,2,3,500]}).distinct(:id)

我得到了以下查询(rails控制台):

SELECT DISTINCT `real_estates`.* FROM `real_estates` INNER JOIN `real_estate_home_features` ON `real_estate_home_features`.`real_estate_id` = `real_estates`.`id` INNER JOIN `re_home_features` ON `re_home_features`.`id` = `real_estate_home_features`.`re_home_feature_id` WHERE `re_home_features`.`id` IN (1, 2, 3, 500)

它返回1个结果。不动产编号7。如果删除不重复项,我将得到3个结果:同一个不动产3次。

预期为零结果。如果params['home_features'] = [1 , 2, 3]我希望得到1个结果。

编辑2

此连接方法有效,但返回的结果类似于“ OR”查询。就我而言,我需要使用“ AND”的联接查询。查询必须为:“退还所有具有特征1 AND 2 AND 3的不动产”。

谢谢!

1 个答案:

答案 0 :(得分:1)

我不确定这是“高级搜索”。 12.1.4 Specifying Conditions on the Joined Tables下的指南中介绍了如何搜索已加入的模型。看起来像这样:

real_estates.joins(:re_home_features).where(re_home_features: {feature_name: 'pool'})

自然,这可能不会完全起作用,因为您没有告诉我们太多有关如何找到“池功能”的信息。但是,它应该给您正确的方向。

顺便说一句,原因如下:

params['home_features'] = [1 , 2, 3, 500]

返回具有any中的home_features的记录是因为它采用了“或”。如果您想将real_estates all home_features一起使用,则需要一个'and'。您可以对此进行搜索。

我想我会尝试:

def search

    r = real_estates.joins(:re_home_features)

    %i(
      code
      city_name
      garage
      restrooms
    ).each do |attribute|
      r = r.where(attribute => params[attribute]) unless params[attribute].blank?
    end

    params[:home_features].each do |home_feature_id|
      r = r.where(re_home_features: {id: home_feature_id})
    end unless params[:home_features].blank?

    r = r.uniq
    r = r.paginate(:page => params[:page], :per_page => 10)
    r

end

注意:我将params[:city]更改为params[:city_name],以使第一个each迭代器更干净。如果您要这样做,则需要更改视图。如果您不想这样做,则可以返回到已有的非迭代器方法。

这没有经过测试,所以我不确定它是否可以完全按照显示的方式工作。