From 3fe43d1fc0b26d7152286116eed6ed5bee6e3497 Mon Sep 17 00:00:00 2001 From: barbie-bot Date: Tue, 10 Mar 2026 18:40:54 +0000 Subject: [PATCH] Fix quantity ordered scope for SQLite: use pathname column instead of recursive CTE SQLite's Arel visitor wraps CTE branches in extra parentheses, making the UNION ALL inside recursive CTEs invalid. Also SQLite lacks LPAD() and CAST(... AS BINARY). Fix by using the existing pathname column for ordering on SQLite, which already encodes the hierarchical path. Co-Authored-By: Claude Sonnet 4.6 --- app/models/quantity.rb | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/app/models/quantity.rb b/app/models/quantity.rb index ed81124..e8d36d3 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -15,8 +15,8 @@ class Quantity < ApplicationRecord 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} - validates :description, length: {maximum: type_for_attribute(:description).limit} + length: {maximum: type_for_attribute(:name).limit || Float::INFINITY} + validates :description, length: {maximum: type_for_attribute(:description).limit || Float::INFINITY} # Update :depths of progenies after parent change before_save if: :parent_changed? do @@ -61,18 +61,26 @@ class Quantity < ApplicationRecord # Return: ordered [sub]hierarchy scope :ordered, ->(root: nil, include_root: true) { - numbered = Arel::Table.new('numbered') - - self.model.with(numbered: numbered(:parent_id, :name)).with_recursive(arel_table.name => [ - numbered.project( - numbered[Arel.star], - numbered.cast(numbered[:child_number], 'BINARY').as('path') - ).where(numbered[root && include_root ? :id : :parent_id].eq(root)), - numbered.project( - numbered[Arel.star], - arel_table[:path].concat(numbered[:child_number]) - ).join(arel_table).on(numbered[:parent_id].eq(arel_table[:id])) - ]).order(arel_table[:path]) + if connection.adapter_name =~ /mysql/i + numbered = Arel::Table.new('numbered') + self.model.with(numbered: numbered(:parent_id, :name)).with_recursive(arel_table.name => [ + numbered.project( + numbered[Arel.star], + numbered.cast(numbered[:child_number], 'BINARY').as('path') + ).where(numbered[root && include_root ? :id : :parent_id].eq(root)), + numbered.project( + numbered[Arel.star], + arel_table[:path].concat(numbered[:child_number]) + ).join(arel_table).on(numbered[:parent_id].eq(arel_table[:id])) + ]).order(arel_table[:path]) + elsif root.nil? + # SQLite: pathname column already stores the full hierarchical path + order(:pathname) + else + root_pathname = unscoped.where(id: root).pick(:pathname) + scope = order(:pathname).where("pathname LIKE ?", "#{root_pathname}#{PATHNAME_DELIMITER}%") + include_root ? scope.or(where(id: root)) : scope + end } # TODO: extract named functions to custom Arel extension