在Rails中过滤包含belongs_to的另一个表的信息的表



我有一个报告belongs_to一台机器,而机器belongs_to一个客户端。我有一个页面可以看到所有的报告。

# report_controller.rb

def index
@reports = Report.all
end

# index.html.erb

<% @reports.each do |report| %>
<tr>
<td><%= report.machine.client.name %></td>
<td><%= report.machine.code %></td>
<td><%= report.observations %></td>
</tr>
<% end %>

现在我需要显示一个特定客户机的所有报告。如果在控制器中输入

@reports = Report.where(observations: "xxx")

工作得很好,但我需要按客户端名称筛选,如

@reports = Report.Machine.Client.where(name: "Joe")

我尝试过类似的事情,阅读作用域和连接,但我找不到工作的东西。

PS:我已经安装了Pundit,也许有一个策略方法

假设(因为你的问题让我们猜测表结构)报告属于机器(报告表有machine_id),机器属于客户端(机器表有client_id),你想通过:name之类的东西获得客户端,你可以这样做:

Report.joins(machine: :client).where(clients: {name: 'Joe'})

将给你SQL:

SELECT "reports".* 
FROM "reports" 
INNER JOIN "machines" 
ON "machines"."id" = "reports"."machine_id" 
INNER JOIN "clients" 
ON "clients"."id" = "machines"."client_id" # Note joins to the previous join
WHERE "clients"."name" = 'Joe'

这个查询的语法与

不同。
Report.joins(:machine, :client).where(clients: {name: 'Joe'})

它会给你:

SELECT "reports".* 
FROM "reports" 
INNER JOIN "machines" 
ON "machines"."id" = "reports"."machine_id" 
INNER JOIN "clients" 
ON "clients"."id" = "reports"."client_id" # Joins on the original table of reports
WHERE "clients"."name" = 'Joe'

非常相似的rails代码,但第一个是在client ->机器→第二个是期待在您的报告上有一个client_id专栏,您可能有,也可能没有。你可能不知道。但是,如果在报表表中有一些OTHER表具有外键,则可以使用第二种语法在两个不同的表上执行内部连接。

还要注意,在你的原始代码中,你正在做:

<% @reports.each do |report| %>
<tr>
<td><%= report.machine.client.name %></td>
<td><%= report.machine.code %></td>
<td><%= report.observations %></td>
</tr>
<% end %>

每次调用<%= report.machine.client.name %>时,将执行三个查询。然后下一行report.machine.code将执行两个查询。因此,将其乘以1000个报告,您将得到数千个查询。如果在原始查询中缓存表信息,则可以摆脱所有额外的查询:

@reports = Report.all.includes(machine: :client)

你将一次加载3个查询,每次循环处理没有新的查询将被触发。可以更好地缩放

你最初的问题也是如此。内部连接查询将返回一个报告列表,每次调用类似上面循环的东西时,都将执行多个查询。所以你应该切换到includes:

Report.includes(machine: :client).where(clients: {name: 'Joe'})

处理这个问题的正确方法是建立间接关联,这样你就不需要违反得墨忒耳定律:

class Report < ApplicationRecord
belongs_to :machine
has_one :client, though: :machine
end
class Client < ApplicationRecord
belongs_to :machine
has_many :reports, through: :machine
end

然后,您可以执行LEFT INNER JOIN,将其过滤到在连接表中有匹配的报告:

Report.joins(:client)
.where(clients: { name: 'Joe' })

现在我需要显示一个特定客户端的所有报告。

你很可能处理错了。而不是通过名称来标识客户端,您应该通过传递id并在记录之外使用关联。

除非你想在客户端的#show页面上显示报告,否则Rails的方法是创建一个嵌套路由:
resources :clients do
resources :reports, 
module: :clients,
only: [:index]
end
# app/controllers/clients/reports_controller.rb
module Clients
# Handles reports for a specific client
class ReportsController < ApplicationController
before_action :set_client

# Displays the clients reports
# GET /clients/1/reports
def index
@reports = @client.reports
end
private
def set_client
@client = Client.eager_load(:reports)
.find(params[:client_id])
end
end
end

使用单独的控制器,而不仅仅是::ReportsController是可选的,但如果你也有一个未嵌套的/reports路由,它可以显示所有的报告,则有助于分离关注点。然后,您可以使用:

链接到此
<%= link_to "Show Reports", [client, :reports] # this assumes that there is a 'client' local %>

相关内容

最新更新