diff --git a/app/controllers/quantities_controller.rb b/app/controllers/quantities_controller.rb index 50c573c..5eda6c2 100644 --- a/app/controllers/quantities_controller.rb +++ b/app/controllers/quantities_controller.rb @@ -1,4 +1,9 @@ class QuantitiesController < ApplicationController + before_action only: :new do + find_quantity if params[:id].present? + end + before_action :find_quantity, only: [:edit, :update, :rebase, :destroy] + before_action except: :index do raise AccessForbidden unless current_user.at_least(:active) end @@ -6,4 +11,34 @@ class QuantitiesController < ApplicationController def index @quantities = current_user.quantities.includes(:parent).includes(:subquantities).ordered end + + def new + @quantity = current_user.quantities.new(parent: @quantity) + end + + def create + @quantity = current_user.quantities.new(quantity_params) + if @quantity.save + @before, @quantity, @ancestors = @quantity.succ_and_ancestors + flash.now[:notice] = t('.success', quantity: @quantity) + else + render :new + end + end + + def destroy + @quantity.destroy! + @ancestors = @quantity.ancestors + flash.now[:notice] = t('.success', quantity: @quantity) + end + + private + + def quantity_params + params.require(:quantity).permit(Quantity::ATTRIBUTES) + end + + def find_quantity + @quantity = Quantity.find_by!(id: params[:id], user: current_user) + end end diff --git a/app/models/quantity.rb b/app/models/quantity.rb index d62b815..89c47b5 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -1,4 +1,6 @@ class Quantity < ApplicationRecord + ATTRIBUTES = [:name, :description, :parent_id] + belongs_to :user, optional: true belongs_to :parent, optional: true, class_name: "Quantity" has_many :subquantities, class_name: "Quantity", inverse_of: :parent, @@ -38,7 +40,7 @@ class Quantity < ApplicationRecord Arel::Nodes::NamedFunction.new('ROW_NUMBER', []) .over(Arel::Nodes::Window.new.partition(parent_column).order(order_column)), Arel::SelectManager.new.project( - Arel::Nodes::NamedFunction.new('LENGTH', [Arel.star.count])]) + Arel::Nodes::NamedFunction.new('LENGTH', [Arel.star.count]) ), Arel::Nodes.build_quoted('0') ], @@ -51,30 +53,54 @@ class Quantity < ApplicationRecord end def movable? - subunits.empty? + subquantities.empty? end def default? parent_id.nil? end - # Return: record, its ancestors and succesive record in order of appearance, - # including :depth attribute. Used for table view reload. - def successive + # Return: self, ancestors and successive record in order of appearance, + # including :depth attribute. Used for partial view reload. + def succ_and_ancestors quantities = Quantity.arel_table ancestors = Arel::Table.new('ancestors') + Quantity.with( ancestors: user.quantities.ordered.select( Arel::Nodes::NamedFunction.new('LAG', [quantities[:id]]).over.as('lag_id') ) ) - .with_recursive(quantities: [ - Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors) - .where(ancestors[:id].eq(id).or(ancestors[:lag_id].eq(id))), - Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors) - .join(quantities).on(quantities[:parent_id].eq(ancestors[:id])) - .where(quantities[:lag_id].not_eq(id)) - ]).order(quantities[:path]) - # return: .first == self ? nul, ancestors : ancestors.pop, ancestors + .with_recursive(quantities: [ + Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors) + .where(ancestors[:id].eq(id).or(ancestors[:lag_id].eq(id))), + Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors) + .join(quantities).on(quantities[:parent_id].eq(ancestors[:id])) + .where(quantities[:lag_id].not_eq(id))]) + .order(quantities[:path]).to_a + .then do |records| + [records.last == self ? nil : records.pop, records.pop, records] + end + end + + def ancestors + quantities = Quantity.arel_table + ancestors = Arel::Table.new('ancestors') + + # Ancestors are listed bottom up, so it's impossible to know depth at the + # start. Start with depth = 0 and count downwards, then adjust by the + # amount needed to set biggest negative depth to 0. + Quantity.with_recursive(ancestors: [ + user.quantities.select(quantities[Arel.star], Arel::Nodes.build_quoted(0).as('depth')) + .where(id: parent_id), + user.quantities.select(quantities[Arel.star], ancestors[:depth] - 1) + .joins(quantities.create_join( + ancestors, quantities.create_on(quantities[:id].eq(ancestors[:parent_id])) + )) + ]).select(ancestors[Arel.star]).from(ancestors).to_a.then do |records| + records.map(&:depth).min&.abs.then do |maxdepth| + records.each { |r| r.depth += maxdepth } + end + end end end diff --git a/app/views/quantities/_form.html.erb b/app/views/quantities/_form.html.erb new file mode 100644 index 0000000..a08128a --- /dev/null +++ b/app/views/quantities/_form.html.erb @@ -0,0 +1,19 @@ +<%= tabular_fields_for @quantity, form: form_tag do |form| %> + <%- tag.tr id: row, class: "form", onkeydown: "processKey(event)", + data: {link: link, form: form_tag, hidden_row: hidden_row} do %> + +