После небольшой пробной ошибки (на основе первого ответа Джейкоба ) я придумал решение, которое работает значительно лучше, убрал сложность с контроллера и просмотрел и сделал все из моего списка требований из моегопервый пост.
Я все еще думаю, что есть место для оптимизации и очистки, но здесь это идет:
app / models /action.rb
scope :by_user_category, ->(user, category) do
where(account: user.accounts, category: category.subtree)
end
app / models / category.rb
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
app / controllers /ateg_controller.rb
def index
@categories = Category.spending_by(current_user)
end
app / views / Categories / index.html.erb
<% @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 %>
app / views / Categories / _category_depth_0.html.erb
<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>
_category_depth_1.html.erb
работает точно так же, как _category_depth_0.html.erb
, но с другой структурой, поэтому я пропустил ее в этом примере.