2017-10-09 83 views
1

我使用分類模型在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 

如果任何人都可以提出一個更好的方法,我很想了解如何優化這是我一直沒能找到一個更好的辦法嘍。

謝謝。

回答

1

有幾種不同的方法可以做到這一點。對於複雜的查詢,我有時會發現編寫和執行直連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

相關問題