Ruby on rails Rails 5.2.3-显示按相关记录总数排序的类别树
我目前正在使用渲染500+个类别和子类别(它们可以达到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
- 这些关联交易的
:金额的总和
- 并按层次对其进行排序
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个类别,所以我没有任何分页(目前)。