From 0652d4a89b0f2874ef1a8c1cb77a50491032bdee Mon Sep 17 00:00:00 2001 From: cryptogopher Date: Sun, 12 Jan 2025 19:15:43 +0100 Subject: [PATCH] Disallow self- and descendant-reference for base/parent --- app/models/quantity.rb | 15 +++++++++++++++ app/models/unit.rb | 1 + config/locales/en.yml | 7 +++++++ 3 files changed, 23 insertions(+) diff --git a/app/models/quantity.rb b/app/models/quantity.rb index dfa3d46..ea0374c 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -8,6 +8,10 @@ class Quantity < ApplicationRecord validate if: ->{ parent.present? } do errors.add(:parent, :user_mismatch) unless user == parent.user + errors.add(:parent, :self_reference) if self == parent + end + validate if: ->{ parent.present? }, on: :update do + errors.add(:parent, :descendant_reference) if ancestor_of?(parent) end validates :name, presence: true, uniqueness: {scope: [:user_id, :parent_id]}, length: {maximum: type_for_attribute(:name).limit} @@ -99,4 +103,15 @@ class Quantity < ApplicationRecord end end end + + def ancestor_of?(descendant) + quantities = Quantity.arel_table + ancestors = Arel::Table.new('ancestors') + Quantity.with_recursive(ancestors: [ + user.quantities.where(id: descendant.id), + user.quantities.joins(quantities.create_join( + ancestors, quantities.create_on(quantities[:id].eq(ancestors[:parent_id])) + )) + ]).from(ancestors).exists?(ancestors: {id: id}) + end end diff --git a/app/models/unit.rb b/app/models/unit.rb index 6aa486e..7b84871 100644 --- a/app/models/unit.rb +++ b/app/models/unit.rb @@ -7,6 +7,7 @@ class Unit < ApplicationRecord validate if: ->{ base.present? } do errors.add(:base, :user_mismatch) unless user == base.user + errors.add(:base, :self_reference) if self == base errors.add(:base, :multilevel_nesting) if base.base.present? end validates :symbol, presence: true, uniqueness: {scope: :user_id}, diff --git a/config/locales/en.yml b/config/locales/en.yml index da77ce8..2f1bb22 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -23,11 +23,18 @@ en: attributes: base: multilevel_nesting: has to be a top-level unit + self_reference: of an unit cannot be the unit itself user_mismatch: has to belong to the same user as unit multiplier: equal_to: for a top-level unit has to be 1 symbol: taken: has to be unique + quantity: + attributes: + parent: + descendant_reference: of the quantity cannot be its descendant + self_reference: of the quantitiy cannot be the quantity itself + user_mismatch: has to belong to the same user as quantity actioncontroller: exceptions: status: