diff --git a/app/models/quantity.rb b/app/models/quantity.rb index bd8ce0b..e8d36d3 100644 --- a/app/models/quantity.rb +++ b/app/models/quantity.rb @@ -61,24 +61,26 @@ class Quantity < ApplicationRecord # Return: ordered [sub]hierarchy scope :ordered, ->(root: nil, include_root: true) { - numbered = Arel::Table.new('numbered') - - path_expr = if connection.adapter_name =~ /mysql/i - numbered.cast(numbered[:child_number], 'BINARY') + 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 - numbered[:child_number] + 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 - - self.model.with(numbered: numbered(:parent_id, :name)).with_recursive(arel_table.name => [ - numbered.project( - numbered[Arel.star], - path_expr.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]) } # TODO: extract named functions to custom Arel extension @@ -86,25 +88,20 @@ class Quantity < ApplicationRecord # be merged with :ordered # https://gist.github.com/ProGM/c6df08da14708dcc28b5ca325df37ceb#extending-arel scope :numbered, ->(parent_column, order_column) { - row_num = Arel::Nodes::NamedFunction.new('ROW_NUMBER', []) - .over(Arel::Nodes::Window.new.partition(parent_column).order(order_column)) - - child_number = if connection.adapter_name =~ /mysql/i + select( + arel_table[Arel.star], Arel::Nodes::NamedFunction.new( 'LPAD', [ - row_num, + 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]) ).from(arel_table), Arel::Nodes.build_quoted('0') - ] - ) - else - Arel::Nodes::NamedFunction.new('format', [Arel::Nodes.build_quoted('%09d'), row_num]) - end - - select(arel_table[Arel.star], child_number.as('child_number')) + ], + ).as('child_number') + ) } def to_s