forked from fixin.me/fixin.me
Add Quantity #new, #create, #destroy actions
This commit is contained in:
parent
1e7ef75e8b
commit
57f10c94a4
@ -1,4 +1,9 @@
|
|||||||
class QuantitiesController < ApplicationController
|
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
|
before_action except: :index do
|
||||||
raise AccessForbidden unless current_user.at_least(:active)
|
raise AccessForbidden unless current_user.at_least(:active)
|
||||||
end
|
end
|
||||||
@ -6,4 +11,34 @@ class QuantitiesController < ApplicationController
|
|||||||
def index
|
def index
|
||||||
@quantities = current_user.quantities.includes(:parent).includes(:subquantities).ordered
|
@quantities = current_user.quantities.includes(:parent).includes(:subquantities).ordered
|
||||||
end
|
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
|
end
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
class Quantity < ApplicationRecord
|
class Quantity < ApplicationRecord
|
||||||
|
ATTRIBUTES = [:name, :description, :parent_id]
|
||||||
|
|
||||||
belongs_to :user, optional: true
|
belongs_to :user, optional: true
|
||||||
belongs_to :parent, optional: true, class_name: "Quantity"
|
belongs_to :parent, optional: true, class_name: "Quantity"
|
||||||
has_many :subquantities, class_name: "Quantity", inverse_of: :parent,
|
has_many :subquantities, class_name: "Quantity", inverse_of: :parent,
|
||||||
@ -38,7 +40,7 @@ class Quantity < ApplicationRecord
|
|||||||
Arel::Nodes::NamedFunction.new('ROW_NUMBER', [])
|
Arel::Nodes::NamedFunction.new('ROW_NUMBER', [])
|
||||||
.over(Arel::Nodes::Window.new.partition(parent_column).order(order_column)),
|
.over(Arel::Nodes::Window.new.partition(parent_column).order(order_column)),
|
||||||
Arel::SelectManager.new.project(
|
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')
|
Arel::Nodes.build_quoted('0')
|
||||||
],
|
],
|
||||||
@ -51,30 +53,54 @@ class Quantity < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def movable?
|
def movable?
|
||||||
subunits.empty?
|
subquantities.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
def default?
|
def default?
|
||||||
parent_id.nil?
|
parent_id.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return: record, its ancestors and succesive record in order of appearance,
|
# Return: self, ancestors and successive record in order of appearance,
|
||||||
# including :depth attribute. Used for table view reload.
|
# including :depth attribute. Used for partial view reload.
|
||||||
def successive
|
def succ_and_ancestors
|
||||||
quantities = Quantity.arel_table
|
quantities = Quantity.arel_table
|
||||||
ancestors = Arel::Table.new('ancestors')
|
ancestors = Arel::Table.new('ancestors')
|
||||||
|
|
||||||
Quantity.with(
|
Quantity.with(
|
||||||
ancestors: user.quantities.ordered.select(
|
ancestors: user.quantities.ordered.select(
|
||||||
Arel::Nodes::NamedFunction.new('LAG', [quantities[:id]]).over.as('lag_id')
|
Arel::Nodes::NamedFunction.new('LAG', [quantities[:id]]).over.as('lag_id')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.with_recursive(quantities: [
|
.with_recursive(quantities: [
|
||||||
Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors)
|
Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors)
|
||||||
.where(ancestors[:id].eq(id).or(ancestors[:lag_id].eq(id))),
|
.where(ancestors[:id].eq(id).or(ancestors[:lag_id].eq(id))),
|
||||||
Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors)
|
Arel::SelectManager.new.project(ancestors[Arel.star]).from(ancestors)
|
||||||
.join(quantities).on(quantities[:parent_id].eq(ancestors[:id]))
|
.join(quantities).on(quantities[:parent_id].eq(ancestors[:id]))
|
||||||
.where(quantities[:lag_id].not_eq(id))
|
.where(quantities[:lag_id].not_eq(id))])
|
||||||
]).order(quantities[:path])
|
.order(quantities[:path]).to_a
|
||||||
# return: .first == self ? nul, ancestors : ancestors.pop, ancestors
|
.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
|
||||||
end
|
end
|
||||||
|
19
app/views/quantities/_form.html.erb
Normal file
19
app/views/quantities/_form.html.erb
Normal file
@ -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 %>
|
||||||
|
|
||||||
|
<td class="<%= class_names({subquantity: @quantity.parent}) %>">
|
||||||
|
<%= form.text_field :name, required: true, autofocus: true, size: 20 %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= form.text_area :description, cols: 30, rows: 1, escape: false %>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td class="actions">
|
||||||
|
<%= form.button %>
|
||||||
|
<%= image_link_to t(:cancel), "close-outline", quantities_path, class: 'dangerous',
|
||||||
|
name: :cancel, onclick: render_turbo_stream('form_close', {row: row}) %>
|
||||||
|
</td>
|
||||||
|
<td></td>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
2
app/views/quantities/_form_close.html.erb
Normal file
2
app/views/quantities/_form_close.html.erb
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<%= turbo_stream.close_form row %>
|
||||||
|
<%= turbo_stream.update :flashes %>
|
28
app/views/quantities/_quantity.html.erb
Normal file
28
app/views/quantities/_quantity.html.erb
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<%= tag.tr id: dom_id(quantity),
|
||||||
|
ondragstart: 'dragStart(event)', ondragend: 'dragEnd(event)',
|
||||||
|
ondragover: 'dragOver(event)', ondrop: 'drop(event)',
|
||||||
|
ondragenter: 'dragEnter(event)', ondragleave: 'dragLeave(event)',
|
||||||
|
data: {drag_path: rebase_quantity_path(quantity),
|
||||||
|
drop_id: dom_id(quantity.parent || quantity)} do %>
|
||||||
|
|
||||||
|
<td class="link" style="--depth:<%= quantity.depth %>">
|
||||||
|
<%= link_to quantity, edit_quantity_path(quantity), onclick: 'this.blur();',
|
||||||
|
data: {turbo_stream: true} %>
|
||||||
|
</td>
|
||||||
|
<td><%= quantity.description %></td>
|
||||||
|
|
||||||
|
<% if current_user.at_least(:active) %>
|
||||||
|
<td class="actions">
|
||||||
|
<%= image_link_to t('.new_subquantity'), 'plus-outline', new_quantity_path(quantity),
|
||||||
|
id: dom_id(quantity, :new, :link), onclick: 'this.blur();', data: {turbo_stream: true} %>
|
||||||
|
|
||||||
|
<%= image_button_to_if quantity.movable?, t('.destroy'), 'delete-outline',
|
||||||
|
quantity_path(quantity), method: :delete %>
|
||||||
|
</td>
|
||||||
|
<% if quantity.movable? %>
|
||||||
|
<td class="handle" draggable="true">⠿</td>
|
||||||
|
<% else %>
|
||||||
|
<td></td>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
7
app/views/quantities/create.turbo_stream.erb
Normal file
7
app/views/quantities/create.turbo_stream.erb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<%= turbo_stream.close_form dom_id(@quantity.parent || Quantity, :new) %>
|
||||||
|
<%= turbo_stream.remove :no_items %>
|
||||||
|
<% @ancestors.map do |ancestor| %>
|
||||||
|
<%= turbo_stream.replace ancestor %>
|
||||||
|
<% end %>
|
||||||
|
<%= @before.nil? ? turbo_stream.append(:quantities, @quantity) :
|
||||||
|
turbo_stream.before(@before, @quantity) %>
|
5
app/views/quantities/destroy.turbo_stream.erb
Normal file
5
app/views/quantities/destroy.turbo_stream.erb
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<% @ancestors.map do |ancestor| %>
|
||||||
|
<%= turbo_stream.replace ancestor %>
|
||||||
|
<% end %>
|
||||||
|
<%= turbo_stream.remove @quantity %>
|
||||||
|
<%= turbo_stream.append(:quantities, render_no_items) if current_user.quantities.empty? %>
|
20
app/views/quantities/new.turbo_stream.erb
Normal file
20
app/views/quantities/new.turbo_stream.erb
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<% dom_obj = @quantity.parent || @quantity %>
|
||||||
|
<% ids = {row: dom_id(dom_obj, :new),
|
||||||
|
hidden_row: nil,
|
||||||
|
link: dom_id(dom_obj, :new, :link),
|
||||||
|
form_tag: dom_id(dom_obj, :new, :form)} %>
|
||||||
|
|
||||||
|
<%= turbo_stream.disable ids[:link] -%>
|
||||||
|
|
||||||
|
<%= turbo_stream.append :quantity_form do %>
|
||||||
|
<%- tabular_form_with model: @quantity, html: {id: ids[:form_tag]} do |form| %>
|
||||||
|
<%= form.hidden_field :parent_id unless @quantity.parent.nil? %>
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if @quantity.parent %>
|
||||||
|
<%= turbo_stream.remove ids[:row] %>
|
||||||
|
<%= turbo_stream.after @quantity.parent, partial: 'form', locals: ids %>
|
||||||
|
<% else %>
|
||||||
|
<%= turbo_stream.prepend :quantities, partial: 'form', locals: ids %>
|
||||||
|
<% end %>
|
@ -56,8 +56,15 @@ en:
|
|||||||
quantities:
|
quantities:
|
||||||
navigation: Quantities
|
navigation: Quantities
|
||||||
no_items: There are no configured quantities. You can Add some or Import from defaults.
|
no_items: There are no configured quantities. You can Add some or Import from defaults.
|
||||||
|
quantity:
|
||||||
|
new_subquantity: Child
|
||||||
|
destroy: Delete
|
||||||
index:
|
index:
|
||||||
new_quantity: New quantity
|
new_quantity: Add quantity
|
||||||
|
create:
|
||||||
|
success: Created new quantity "%{quantity}"
|
||||||
|
destroy:
|
||||||
|
success: Deleted quantity "%{quantity}"
|
||||||
units:
|
units:
|
||||||
navigation: Units
|
navigation: Units
|
||||||
no_items: There are no configured units. You can Add some or Import from defaults.
|
no_items: There are no configured units. You can Add some or Import from defaults.
|
||||||
@ -68,8 +75,6 @@ en:
|
|||||||
new_unit: Add unit
|
new_unit: Add unit
|
||||||
import_units: Import
|
import_units: Import
|
||||||
top_level_drop: Drop here to reposition into top-level unit
|
top_level_drop: Drop here to reposition into top-level unit
|
||||||
new:
|
|
||||||
none: none
|
|
||||||
create:
|
create:
|
||||||
success: Created new unit "%{unit}"
|
success: Created new unit "%{unit}"
|
||||||
update:
|
update:
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
resources :quantities
|
resources :quantities, except: [:show], path_names: {new: '(/:id)/new'} do
|
||||||
|
member { post :rebase }
|
||||||
|
end
|
||||||
|
|
||||||
resources :units, except: [:show], path_names: {new: '(/:id)/new'} do
|
resources :units, except: [:show], path_names: {new: '(/:id)/new'} do
|
||||||
member { post :rebase }
|
member { post :rebase }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user