Ruby on rails Rails 5.2.3-显示按相关记录总数排序的类别树

Ruby on rails Rails 5.2.3-显示按相关记录总数排序的类别树,ruby-on-rails,ruby,ruby-on-rails-5,Ruby On Rails,Ruby,Ruby On Rails 5,我目前正在使用渲染500+个类别和子类别(它们可以达到3级深度) 现在,我想做的是: 仅显示关联的类别/子类别 交易 按以下顺序排列这些类别: 这些关联交易的:金额的总和 并按层次对其进行排序 打印每个类别名称旁边的总数 下面是我希望实现的一个例子: Travel = $1500 Travel > Air = $1000 Travel > Ground = $250 Business = $500 Business > Services = $250 Business &g

我目前正在使用渲染500+个类别和子类别(它们可以达到3级深度)

现在,我想做的是:

  • 仅显示关联的类别/子类别 交易
  • 按以下顺序排列这些类别:
    • 这些关联交易的
      :金额的总和
    • 并按层次对其进行排序
  • 打印每个类别名称旁边的总数
  • 下面是我希望实现的一个例子:

    Travel = $1500
    Travel > Air = $1000
    Travel > Ground = $250
    Business = $500
    Business > Services = $250
    Business > Services > Marketing = $75
    # etc...
    
    这就是我的模型的样子:

    class Category < ApplicationRecord
      has_many :transactions
      has_ancestry
    end
    
    class Transaction < ApplicationRecord
      belongs_to :account
      belongs_to :category
    end
    
    类别
    到目前为止,我几乎能够做到:

    # app/controllers/categories_controller.rb
    def index
      # Get all of the root categories
      @primary_categories = Category.where(ancestry: nil)
    end
    
    # app/views/categories/index.html.erb
    <% @primary_categories.each do |primary_category| %>
      <% primary_category_total = Transaction.where(account_id: current_user, category_id: primary_category.subtree).sum(:amount) %>
    
      <% if primary_category_total != 0.0 %> 
        <%= link_to primary_category.name, category_path(primary_category) %>
        <%= number_to_currency primary_category_total %>
    
        <% if primary_category.has_children? && primary_category_total != 0.0 %> 
          <% primary_category.children.each do |secondary_category| %>
            <% secondary_category_total = Transaction.where(account_id: current_user, category_id: primary_category.subtree).sum(:amount) %>
    
            <% if secondary_category_total != 0.0 %> 
              <%= link_to secondary_category.name, category_path(secondary_category) %>
              <%= number_to_currency secondary_category_total %>
    
              <% if secondary_category.has_children? && secondary_category_total != 0.0 %>
                <% secondary_category.children.each do |tertiary_category| %>
                <% tertiary_category_total = Transaction.where(account_id: current_user, category_id: primary_category.subtree).sum(:amount) %>
    
                <% if tertiary_category_total != 0.0 %> 
                  <%= link_to tertiary_category.name, category_path(tertiary_category) %>
                  <%= number_to_currency tertiary_category.transactions.sum(:amount) %>
    # etc...
    
    #app/controllers/categories_controller.rb
    def索引
    #获取所有根类别
    @primary_categories=类别。其中(祖先:无)
    结束
    #app/views/categories/index.html.erb
    #等等。。。
    
    但这会产生大量令人痛苦的查询,而且速度非常慢,更不用说我现在的视图中有一堆复杂的代码。当然,它们不是按总数排序的


    我还应该怎么做呢?

    买主须知:

    下面的所有内容都应视为伪代码。我没有测试任何一个,我没有试图让它复制粘贴就绪。这只是帮助您开始重构的一个起点

    在您的模型中 您希望从视图中获取类似于
    Transaction.where(帐户\u id:current\u用户,类别\u id:primary\u category.subtree)的内容。sum(:amount)

    您可以创建一个方法和/或范围来返回事务总数。例如:

    # app/views/categories/_category.html.erb
    <% @categories.each do |category| %>
      <% category_total = category.user_transaction_amount(current_user) %>
      <% return if category_total == 0.0 %>
      <%= link_to category.name, category_path(category) %>
      <%= number_to_currency category_total %>
      <% if category.has_children? && category_total != 0.0 %> 
          <% category.children.each do |secondary_category| %>
            <%= render category, category: secondary_category %>
          <% end %>
      <% end %>
    <% end %>
    
    
    # app/views/categories/index.html.erb
    <% @primary_categories.each do |primary_category| %>
      <% render 'categories', category: primary_category %>
    <% end %>
    
    #app/models/transaction.rb
    范围:按用户分类,->(用户,分类)do
    其中(帐户:用户,类别:category.subtree)
    结束
    #app/models/category.rb
    def用户\交易\金额(用户)
    交易。按用户分类(用户、自身)。金额(:金额)
    结束
    
    在控制器中 你应该照顾孩子们。你知道你会需要它们,所以马上去做。(有理由的,见下文)

    你提到500多个类别,这是分页的吗?如果是,请在中进行

    在你看来 注意重复的代码?试着用刷子把它擦干。例如:

    # app/views/categories/_category.html.erb
    <% @categories.each do |category| %>
      <% category_total = category.user_transaction_amount(current_user) %>
      <% return if category_total == 0.0 %>
      <%= link_to category.name, category_path(category) %>
      <%= number_to_currency category_total %>
      <% if category.has_children? && category_total != 0.0 %> 
          <% category.children.each do |secondary_category| %>
            <%= render category, category: secondary_category %>
          <% end %>
      <% end %>
    <% end %>
    
    
    # app/views/categories/index.html.erb
    <% @primary_categories.each do |primary_category| %>
      <% render 'categories', category: primary_category %>
    <% end %>
    
    #app/views/categories/_category.html.erb
    #app/views/categories/index.html.erb
    

    这仍然需要一些改进。视图模板应该主要是html语句,这是100%ruby。你可能会把这些都放到一个助手中,或者甚至直接从一个控制器操作中删除。

    经过一段时间的尝试,一个错误(基于Jacob的错误)我想出了一个解决方案,它的性能显著提高,消除了控制器和视图的复杂性,并且完成了我第一篇文章中要求列表中的所有事情

    我仍然认为有优化和清理的空间,但这里是:

    app/models/transaction.rb

      scope :by_user_category, ->(user, category) do 
        where(account: user.accounts, category: category.subtree)
      end
    
      def balance(user)
        Transaction.by_user_category(user, self).sum(:amount)
      end
    
      def self.spending_by(user)
        categories_with_spending = []
    
        categories_depth_0 = Category.where(ancestry: nil) # Get the root categories
        categories_depth_0.each do |cat_depth_0|
          category_depth_0_balance = cat_depth_0.balance(user)
    
          if category_depth_0_balance < 0  # "Root category exists and has a balance"
            categories_depth_1_with_spending = []
            categories_depth_1 = Category.find_by_id(cat_depth_0).children # Get the sub-categories
    
            if !categories_depth_1.empty? # "Sub-category exists"
              categories_depth_1.each do |cat_depth_1|
                category_depth_1_balance = cat_depth_1.balance(user)
    
                if category_depth_1_balance < 0 # "Sub-category exists and has a balance"
                  categories_depth_2_with_spending = []
                  categories_depth_2 = Category.find_by_id(cat_depth_1).children
    
                  if !categories_depth_2.empty? # Sub-category has child
                    categories_depth_2.each do |cat_depth_2|
                      category_depth_2_balance = cat_depth_2.balance(user)
    
                      if category_depth_2_balance < 0  # Sub-category child has a balance
                        categories_depth_2_with_spending << {
                          category: cat_depth_2,
                          balance: category_depth_2_balance
                        }
                      end
                    end
                  end
    
                  if categories_depth_2_with_spending != nil
                    # Passing child sub-categories to parent sub-categories
                    categories_depth_1_with_spending << {
                      category: cat_depth_1,
                      balance: category_depth_1_balance,
                      sub_categories: categories_depth_2_with_spending.sort_by { |c| c[:balance] }
                    }
                  end
                end
              end
    
              if categories_depth_1_with_spending != nil
                # Passing sub-categories to root category
                categories_with_spending << {
                  category: cat_depth_0,
                  balance: category_depth_0_balance,
                  sub_categories: categories_depth_1_with_spending.sort_by { |c| c[:balance] }
                }
              end
            else
              # "Root exists but has no sub-categories"
              categories_with_spending << {
                category: cat_depth_0,
                balance: category_depth_0_balance
              }
            end
          end
        end
    
        return categories_with_spending.sort_by { |c| c[:balance] }
      end
    
      def index
        @categories = Category.spending_by(current_user)
      end
    
      <% @categories.each do |cat_depth_0| %>
        <section class="app-card app-amount-summary app-categories__card">
          <%= render 'category_depth_0', category: cat_depth_0 %>
    
          <% if cat_depth_0[:sub_categories] %>
            <ul class="app-category-list">
              <% cat_depth_0[:sub_categories].each do |cat_depth_1| %>
                <%= render 'category_depth_1', category: cat_depth_1 %>
              <% end %>
            </ul>
          <% end %>
        </section>
      <% end %>
    
    <header class="app-card-header">
      <h3 class="app-card-header__h3">
        <%= link_to category[:category].name, category_path(category[:category].id) %>
      </h3>
      <p class="app-card-header__balance"><%= number_to_currency category[:balance] %></p>
    </header>
    
    app/models/category.rb

      scope :by_user_category, ->(user, category) do 
        where(account: user.accounts, category: category.subtree)
      end
    
      def balance(user)
        Transaction.by_user_category(user, self).sum(:amount)
      end
    
      def self.spending_by(user)
        categories_with_spending = []
    
        categories_depth_0 = Category.where(ancestry: nil) # Get the root categories
        categories_depth_0.each do |cat_depth_0|
          category_depth_0_balance = cat_depth_0.balance(user)
    
          if category_depth_0_balance < 0  # "Root category exists and has a balance"
            categories_depth_1_with_spending = []
            categories_depth_1 = Category.find_by_id(cat_depth_0).children # Get the sub-categories
    
            if !categories_depth_1.empty? # "Sub-category exists"
              categories_depth_1.each do |cat_depth_1|
                category_depth_1_balance = cat_depth_1.balance(user)
    
                if category_depth_1_balance < 0 # "Sub-category exists and has a balance"
                  categories_depth_2_with_spending = []
                  categories_depth_2 = Category.find_by_id(cat_depth_1).children
    
                  if !categories_depth_2.empty? # Sub-category has child
                    categories_depth_2.each do |cat_depth_2|
                      category_depth_2_balance = cat_depth_2.balance(user)
    
                      if category_depth_2_balance < 0  # Sub-category child has a balance
                        categories_depth_2_with_spending << {
                          category: cat_depth_2,
                          balance: category_depth_2_balance
                        }
                      end
                    end
                  end
    
                  if categories_depth_2_with_spending != nil
                    # Passing child sub-categories to parent sub-categories
                    categories_depth_1_with_spending << {
                      category: cat_depth_1,
                      balance: category_depth_1_balance,
                      sub_categories: categories_depth_2_with_spending.sort_by { |c| c[:balance] }
                    }
                  end
                end
              end
    
              if categories_depth_1_with_spending != nil
                # Passing sub-categories to root category
                categories_with_spending << {
                  category: cat_depth_0,
                  balance: category_depth_0_balance,
                  sub_categories: categories_depth_1_with_spending.sort_by { |c| c[:balance] }
                }
              end
            else
              # "Root exists but has no sub-categories"
              categories_with_spending << {
                category: cat_depth_0,
                balance: category_depth_0_balance
              }
            end
          end
        end
    
        return categories_with_spending.sort_by { |c| c[:balance] }
      end
    
      def index
        @categories = Category.spending_by(current_user)
      end
    
      <% @categories.each do |cat_depth_0| %>
        <section class="app-card app-amount-summary app-categories__card">
          <%= render 'category_depth_0', category: cat_depth_0 %>
    
          <% if cat_depth_0[:sub_categories] %>
            <ul class="app-category-list">
              <% cat_depth_0[:sub_categories].each do |cat_depth_1| %>
                <%= render 'category_depth_1', category: cat_depth_1 %>
              <% end %>
            </ul>
          <% end %>
        </section>
      <% end %>
    
    <header class="app-card-header">
      <h3 class="app-card-header__h3">
        <%= link_to category[:category].name, category_path(category[:category].id) %>
      </h3>
      <p class="app-card-header__balance"><%= number_to_currency category[:balance] %></p>
    </header>
    
    app/views/categories/index.html.erb

      scope :by_user_category, ->(user, category) do 
        where(account: user.accounts, category: category.subtree)
      end
    
      def balance(user)
        Transaction.by_user_category(user, self).sum(:amount)
      end
    
      def self.spending_by(user)
        categories_with_spending = []
    
        categories_depth_0 = Category.where(ancestry: nil) # Get the root categories
        categories_depth_0.each do |cat_depth_0|
          category_depth_0_balance = cat_depth_0.balance(user)
    
          if category_depth_0_balance < 0  # "Root category exists and has a balance"
            categories_depth_1_with_spending = []
            categories_depth_1 = Category.find_by_id(cat_depth_0).children # Get the sub-categories
    
            if !categories_depth_1.empty? # "Sub-category exists"
              categories_depth_1.each do |cat_depth_1|
                category_depth_1_balance = cat_depth_1.balance(user)
    
                if category_depth_1_balance < 0 # "Sub-category exists and has a balance"
                  categories_depth_2_with_spending = []
                  categories_depth_2 = Category.find_by_id(cat_depth_1).children
    
                  if !categories_depth_2.empty? # Sub-category has child
                    categories_depth_2.each do |cat_depth_2|
                      category_depth_2_balance = cat_depth_2.balance(user)
    
                      if category_depth_2_balance < 0  # Sub-category child has a balance
                        categories_depth_2_with_spending << {
                          category: cat_depth_2,
                          balance: category_depth_2_balance
                        }
                      end
                    end
                  end
    
                  if categories_depth_2_with_spending != nil
                    # Passing child sub-categories to parent sub-categories
                    categories_depth_1_with_spending << {
                      category: cat_depth_1,
                      balance: category_depth_1_balance,
                      sub_categories: categories_depth_2_with_spending.sort_by { |c| c[:balance] }
                    }
                  end
                end
              end
    
              if categories_depth_1_with_spending != nil
                # Passing sub-categories to root category
                categories_with_spending << {
                  category: cat_depth_0,
                  balance: category_depth_0_balance,
                  sub_categories: categories_depth_1_with_spending.sort_by { |c| c[:balance] }
                }
              end
            else
              # "Root exists but has no sub-categories"
              categories_with_spending << {
                category: cat_depth_0,
                balance: category_depth_0_balance
              }
            end
          end
        end
    
        return categories_with_spending.sort_by { |c| c[:balance] }
      end
    
      def index
        @categories = Category.spending_by(current_user)
      end
    
      <% @categories.each do |cat_depth_0| %>
        <section class="app-card app-amount-summary app-categories__card">
          <%= render 'category_depth_0', category: cat_depth_0 %>
    
          <% if cat_depth_0[:sub_categories] %>
            <ul class="app-category-list">
              <% cat_depth_0[:sub_categories].each do |cat_depth_1| %>
                <%= render 'category_depth_1', category: cat_depth_1 %>
              <% end %>
            </ul>
          <% end %>
        </section>
      <% end %>
    
    <header class="app-card-header">
      <h3 class="app-card-header__h3">
        <%= link_to category[:category].name, category_path(category[:category].id) %>
      </h3>
      <p class="app-card-header__balance"><%= number_to_currency category[:balance] %></p>
    </header>
    
    
    
    app/views/categories/\u category\u depth\u 0.html.erb

      scope :by_user_category, ->(user, category) do 
        where(account: user.accounts, category: category.subtree)
      end
    
      def balance(user)
        Transaction.by_user_category(user, self).sum(:amount)
      end
    
      def self.spending_by(user)
        categories_with_spending = []
    
        categories_depth_0 = Category.where(ancestry: nil) # Get the root categories
        categories_depth_0.each do |cat_depth_0|
          category_depth_0_balance = cat_depth_0.balance(user)
    
          if category_depth_0_balance < 0  # "Root category exists and has a balance"
            categories_depth_1_with_spending = []
            categories_depth_1 = Category.find_by_id(cat_depth_0).children # Get the sub-categories
    
            if !categories_depth_1.empty? # "Sub-category exists"
              categories_depth_1.each do |cat_depth_1|
                category_depth_1_balance = cat_depth_1.balance(user)
    
                if category_depth_1_balance < 0 # "Sub-category exists and has a balance"
                  categories_depth_2_with_spending = []
                  categories_depth_2 = Category.find_by_id(cat_depth_1).children
    
                  if !categories_depth_2.empty? # Sub-category has child
                    categories_depth_2.each do |cat_depth_2|
                      category_depth_2_balance = cat_depth_2.balance(user)
    
                      if category_depth_2_balance < 0  # Sub-category child has a balance
                        categories_depth_2_with_spending << {
                          category: cat_depth_2,
                          balance: category_depth_2_balance
                        }
                      end
                    end
                  end
    
                  if categories_depth_2_with_spending != nil
                    # Passing child sub-categories to parent sub-categories
                    categories_depth_1_with_spending << {
                      category: cat_depth_1,
                      balance: category_depth_1_balance,
                      sub_categories: categories_depth_2_with_spending.sort_by { |c| c[:balance] }
                    }
                  end
                end
              end
    
              if categories_depth_1_with_spending != nil
                # Passing sub-categories to root category
                categories_with_spending << {
                  category: cat_depth_0,
                  balance: category_depth_0_balance,
                  sub_categories: categories_depth_1_with_spending.sort_by { |c| c[:balance] }
                }
              end
            else
              # "Root exists but has no sub-categories"
              categories_with_spending << {
                category: cat_depth_0,
                balance: category_depth_0_balance
              }
            end
          end
        end
    
        return categories_with_spending.sort_by { |c| c[:balance] }
      end
    
      def index
        @categories = Category.spending_by(current_user)
      end
    
      <% @categories.each do |cat_depth_0| %>
        <section class="app-card app-amount-summary app-categories__card">
          <%= render 'category_depth_0', category: cat_depth_0 %>
    
          <% if cat_depth_0[:sub_categories] %>
            <ul class="app-category-list">
              <% cat_depth_0[:sub_categories].each do |cat_depth_1| %>
                <%= render 'category_depth_1', category: cat_depth_1 %>
              <% end %>
            </ul>
          <% end %>
        </section>
      <% end %>
    
    <header class="app-card-header">
      <h3 class="app-card-header__h3">
        <%= link_to category[:category].name, category_path(category[:category].id) %>
      </h3>
      <p class="app-card-header__balance"><%= number_to_currency category[:balance] %></p>
    </header>
    
    
    


    \u category\u depth\u 1.html.erb
    的工作原理与
    \u category\u depth\u 0.html.erb
    完全相同,但结构不同,因此我跳过了这个示例。

    谢谢Jacob,我将尝试一下您的方法。澄清一下,虽然我有500多个类别,但我只希望在我运行完所有事务总计后呈现30个类别,所以我没有任何分页(目前)。