在Rails中对具有许多关系的查询进行分组
问题描述:
我使用分类模型在Projets和类别之间建立了has_many, through:
关系。一个Projet belongs_to
一个客户端。在Rails中对具有许多关系的查询进行分组
class Client < ApplicationRecord
has_many :projets
end
class Category < ApplicationRecord
has_many :categorizations, dependent: :destroy
has_many :projets, through: :categorizations
end
class Categorization < ApplicationRecord
belongs_to :category
belongs_to :projet
end
class Projet < ApplicationRecord
belongs_to :client
has_many :categorizations, dependent: :destroy
has_many :categories, through: :categorizations
end
对于一个特定的类别,我想列出所有projets,由客户端分组。例如
(对于CATEGORY_ID = 3)
客户端A PROJET 1 PROJET 2
客户端B PROJET 3
客户端C PROJET 4
到目前为止我能得到这个工作,但只能通过使用两个查询(其中一个是非常低效的(n + 1问题))
这是代码
def listing
@projets_clients = Projet
.select("client_id")
.includes(:client)
.joins(:categorizations)
.where(categorizations: { category: @category })
.group("client_id")
@clients = []
@projets_clients.each do |p|
@clients << Client.includes(:projets).find(p.client_id)
end
end
如果任何人都可以提出一个更好的方法,我很想了解如何优化这是我一直没能找到一个更好的办法喽。
谢谢。
答
有几种不同的方法可以做到这一点。对于复杂的查询,我有时会发现编写和执行直连SQL更容易。然而,对于你的情况,根据数据大小,你可以急于加载数据并将其变成散列。
注意:当我测试此代码时,我使用projects
而不是projets
。
@category = Category.includes(projects: [:client]).find(2)
@projects_by_client = @category.projects.group_by(&:client_id)
# In your view
<%- @projects_by_client.each do |client_id, projects| %>
<%= projects.first.client.name %>
<%- projects.each do |project| %>
<%= project.name %>
<% end %>
<% end %>
一个更充实的解决方案可以使用完整的SQL与查询对象和演示对象。我使用下面的代码创建了一个快速项目,输出结果就是你正在寻找的东西。
# app/controllers/clients_controller.rb
class ClientsController < ApplicationController
def show
result = ClientQuery.call(params[:id])
@presenter = ClientPresenter.new(result)
end
end
# app/services/client_query.rb
class ClientQuery
class << self
def call(client_id)
sql_query(client_id)
end
protected
def sql_query(client_id)
ActiveRecord::Base.
connection.
execute(
sanitized_sql_statement(client_id)
)
end
def sanitized_sql_statement(client_id)
ActiveRecord::Base.send(
:sanitize_sql_array,
[
sql_statement,
client_id
]
)
end
def sql_statement
<<-SQL
SELECT
c.id AS client_id,
c.name AS client_name,
p.name AS project_name
FROM
clients c
INNER JOIN
projects p ON p.client_id = c.id
INNER JOIN
categorizations cz ON cz.project_id = p.id
INNER JOIN
categories ct ON ct.id = cz.category_id
WHERE
ct.id = ?;
SQL
end
end
end
# app/presenters/client_presenter.rb
class ClientPresenter
attr_reader :clients
def initialize(data)
@clients = {}
process_sql_result(data)
end
private
def process_sql_result(data)
data.each do |row|
client_id = row['client_id']
@clients[client_id] ||= { client_name: row['client_name'] }
@clients[client_id][:projects] ||= []
@clients[client_id][:projects] << row['project_name']
end
end
end
# app/views/show.html.erb
<%- @presenter.clients.each do |client_id, client_presenter| %>
<h1><%= client_presenter[:client_name] %></h1>
<ul>
<%- client_presenter[:projects].each do |project_name| %>
<li><%= project_name %></li>
<% end %>
</ul>
<% end %>
这当然只是您在单个查询中获取数据并呈现数据的许多方法之一。
哇,非常感谢你的细节答案! SQL方法非常有趣。对不起,与'projets'(它的法文)混淆。 虽然在你的第一个例子中的代码有一个小错误,但感谢您的慷慨帮助(如何嵌套包括!)我想我弄明白了它是什么: @projects_by_client = @ category.projects.group_by(& client_id) (还需要添加.projects - 并且'@clients ='这一行也与我无关) 也许这对其他人来说更新这个&然后我可以将它标记为正确的答案? 现在它完美的工作,非常感谢你! – Chris
我更新了代码以修复您发现的错误。 – Genzume