我有一个报告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 %>