在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 %> 

这当然只是您在单个查询中获取数据并呈现数据的许多方法之一。

+0

哇,非常感谢你的细节答案! SQL方法非常有趣。对不起,与'projets'(它的法文)混淆。 虽然在你的第一个例子中的代码有一个小错误,但感谢您的慷慨帮助(如何嵌套包括!)我想我弄明白了它是什么: @projects_by_client = @ category.projects.group_by(& client_id) (还需要添加.projects - 并且'@clients ='这一行也与我无关) 也许这对其他人来说更新这个&然后我可以将它标记为正确的答案? 现在它完美的工作,非常感谢你! – Chris

+1

我更新了代码以修复您发现的错误。 – Genzume