Rails加入和HABTM的关系

时间:2014-05-14 12:12:18

标签: ruby-on-rails ruby ruby-on-rails-4 has-and-belongs-to-many

在我的应用程序中,我有模型车:

has_and_belongs_to_many :locations

现在我正在寻找搜索,我想搜索已经提供位置的汽车。 在我看来,我有:

.row
  = horizontal_simple_form_for :cars, {url: cars_path, method: :get} do |f|
     = f.input :handover_location, label: I18n.t('.handover'), collection: Location.all.map{|hl| [hl.location_address, hl.id]}
     = f.input :return_location, label: I18n.t('.return') ,collection: Location.all.map{|rl| [rl.location_address, rl.id]}
     = f.submit class: 'btn btn-success' 

在我的控制器中,我根据params过滤结果:

@cars = Car.joins(:locations).where("locations.id= ? AND locations.id= ?", params[:cars][:handover_location], params[:cars][:return_location])

但是这段代码不能正常工作。也许我不应该两次使用“locations.id”?

2 个答案:

答案 0 :(得分:3)

我将假设您的联接表名为cars_locations。如果您只想在sql中执行此操作,则可以将此表连接到自身

... cars_locations cl1 join cars_locations cl2 on e1.car_id = e2.car_id ...

...这将使用此结构在查询期间生成伪表:

cl1.id | cl1.car_id | cl1.location_id | cl2.id | cl2.car_id | cl2.location_id

然后查询所需的location_id - 这将为您提供在两个位置都有相同汽车的条目 - 让我们说拾取和返回位置的ID是123和456:

select distinct(cl1.car_id) from cars_locations cl1 join cars_locations cl2 on cl1.car_id = cl2.car_id where (c11.location_id = 123 and cl2.location_id = 456) or (cl1.location_id = 123 and cl2.location_id = 456);

现在我们知道了sql,你可以把它包装成Car类的方法

#in the Car class
def self.cars_at_both_locations(location1, location2)
  self.find_by_sql("select * from cars where id in (select distinct(cl1.car_id) from cars_locations cl1 join cars_locations cl2 on cl1.car_id = cl2.car_id where (c11.location_id = #{location1.id} and cl2.location_id = #{location2.id}) or (cl1.location_id = #{location2.id} and cl2.location_id = #{location1.id}))")
end

这不是最有效的方法,因为大表上的连接开始变得非常慢。 更快的方法是

def self.cars_at_both_locations(location1, location2)
  self.find(location1.car_ids & location2.car_ids)
end

在这种情况下,我们使用&这是" set intersection"运算符(不要与&&混淆):即它只会返回两端数组中的值。

答案 1 :(得分:1)

你肯定不应该在where子句中使用locations.id两次,因为这在物理上是不可能的。由此得到的查询将基本上尝试找到它的id是切换位置和返回位置的位置。所以从本质上讲,你要求的是

where 1 == 1 AND 1 == 2

不用说,什么都不会返回。

理论上,如果您只是更改AND的{​​{1}},那么您将获得您之后所拥有的内容。这样,您就可以向数据库询问具有ID或ORstart_location

的任何位置

<强>更新

重新阅读问题。这比我最初想的那样有点诡计,所以你可能需要对结果进行一些处理。正如我所说的,使用AND查询的方式是向数据库询问不可能的事情,但是如我原先所说的那样使用OR会导致汽车具有EITHER或位置,而不是两者。这可以在原始SQL中完成,但是使用Rails这既尴尬又不受欢迎,所以这是另一种解决方案。

使用我最初提出的OR选择器查询数据,因为这会大大减少数据集。然后手动完成它,并拒绝任何不具备这两个位置的内容:

handover_location

这样做的是查询所有要求位置的汽车。然后它循环通过这些汽车,拒绝任何其位置ID列表不包含所提供的IDS。现在剩余的汽车在两个地方都可用:)