表和.where()条件之间的Rails .joins()

时间:2017-03-12 12:12:09

标签: ruby-on-rails ruby ruby-on-rails-5

摘要

非常感谢你的帮助。 我有一个locationsads表。位置has_many :ads

我想查询带有Location join模型的Ad模型,以过滤LocationAd参数的条目。

@locations = Location.joins(:ads).where(locations: location_params, ads: location_params[:ads_attributes])

这是location_params方法(将使用另一种正常工作的方法删除空字段。)

params.require(:location).permit(:country, {:ads_attributes => [:remote, :days]})

这是我的查询示例。我有一个方法从location_params中删除空字段。它工作正常。

SELECT "locations".* FROM "locations" INNER JOIN "ads" 
ON "ads"."location_id" = "locations"."id" 
WHERE "ads_attributes"."remote" = $1  [["remote", "1"]]

在这种情况下,location_params包含以下字段:

<ActionController::Parameters {"ads_attributes"=>
<ActionController::Parameters {"remote"=>"1"} permitted: true>} permitted: true>

这是结果,即使我在表格中有这些参数的条目

,也是一个空对象
#<Location::ActiveRecord_Relation:0x3fb83def8190>

更新

  1. 第一期 - 从Péter Tóth
  2. 解决

    解。使用.includes(:ads)不使用@locations[0].ads重新执行查询。

    @locations = Location.joins(:ads).where(locations: {id: 1}, ads: {id: 1})
    

    问题是,当我从位置选择ads时,它会再次执行查询并删除之前的过滤器ads.id = 1.

    @locations[0].ads
    

    结果是,不仅会选择ID = 1的广告,而且会选择该@location [0]的所有广告。

    1. 第二期
    2. 我可以执行查询:

      @locations = Location.joins(:ads).where(locations: {id: 1}, ads: {id: 1})
      

      或者

      @locations = Location.joins(:ads).where(location_params)
      

      但不是

      @locations = Location.joins(:ads).where(locations: location_params, ads: ads_params)
      

      但这可以解决如下: 对位置执行第一次查询

      @locations = Location.joins(:ads).where(@location_params.require(:location).permit(:id)).includes(:ads)
      

      返回包含这些参数的所有位置,然后我需要根据广告过滤器过滤@locations。问题是我无法执行以下查询。

      @locations = @locations.joins(:ads).where(ads: @ads_params.require(:ads).permit(:id)).includes(:ads)
      

2 个答案:

答案 0 :(得分:4)

您需要重写查询,因为ads_attributes不是表格,请尝试此操作

ads_params = @ads_params.require(:ads).permit(:id).to_h
location_params = @location_params.require(:location).permit(:id).to_h
@locations = Location.joins(:ads).where(locations: location_params, ads: ads_params)

希望有所帮助!

答案 1 :(得分:3)

无论如何,

@locations[0].ads都会获取第一个位置对象的所有ads。当然,如果尚未获取它们,它将仅执行获取过程。一种解决方案是侧载ads

@locations = Location.joins(:ads).where(locations: {id: 1}, ads: {id: 1}).includes(:ads)
@locations[0].ads

这样可以避免N + 1查询问题。但是你应该小心使用它,因为@locations[0].ads.reload将加载所有ads,无论你之前使用的是什么过滤器。

提示:这取决于您的目的是什么,如果您只根据某些标准需要ads,那么我建议您从Ad.join(:location).where(...).includes(:location)...开始

<强>更新

如果我发送GET /locations?location[country]=IT&ad[remote]=1

,这对我有用
class LocationsController < ApplicationController
  def index
    @locations = Location.joins(:ads).where(locations: location_filters, ads: ad_filters).includes(:ads)
  end

  private

  def location_filters
    params.require(:location).permit(:country)
  end

  def ad_filters
    params.require(:ad).permit(:remote, :days)
  end
end

或者如果你没有在每种情况下都提供所有的婴儿车,那么你可能想要构建一个查询:

class LocationsController < ApplicationController
  def index
    @locations = Location.joins(:ads).includes(:ads)
    @locations = @locations.where(locations: location_filters) if location_filters.present?
    @locations = @locations.where(ads: ad_filters) if ad_filters.present?
  end

  private

  def location_filters
    params.fetch(:location, {}).permit(:country)
  end

  def ad_filters
    params.fetch(:ad, {}).permit(:remote, :days)
  end
end