lib/order_query/space.rb
# frozen_string_literal: true
require 'order_query/column'
require 'order_query/sql/order_by'
module OrderQuery
# Order specification and a scope
class Space
# @return [Array<OrderQuery::Column>]
attr_reader :columns
delegate :count, :empty?, to: :@base_scope
# @param [ActiveRecord::Relation] base_scope
# @param [Array<Array<Symbol,String>>] order_spec
# @see Column#initialize for the order_spec element format.
def initialize(base_scope, order_spec)
@base_scope = base_scope
@columns = order_spec.map(&:clone)
@columns.map! do |cond_spec|
build_column(base_scope, cond_spec)
end
# add primary key if columns are not unique
unless @columns.last.unique?
if @columns.detect(&:unique?)
fail ArgumentError, 'Unique column must be last'
end
@columns << Column.new(base_scope, base_scope.primary_key)
end
@order_by_sql = SQL::OrderBy.new(@columns)
end
# @return [Point]
def at(record)
Point.new(record, self)
end
# @return [ActiveRecord::Relation] scope ordered by columns
def scope
@scope ||= @base_scope.order(Arel.sql(@order_by_sql.build))
end
# @return [ActiveRecord::Relation] scope ordered by columns in reverse
def scope_reverse
@scope_reverse ||= @base_scope
.order(Arel.sql(@order_by_sql.build_reverse))
end
# @return [ActiveRecord::Base]
def first
scope.first
end
# @return [ActiveRecord::Base]
def last
scope_reverse.first
end
def inspect
"#<OrderQuery::Space @columns=#{@columns.inspect} "\
"@base_scope=#{@base_scope.inspect}>"
end
private
def build_column(base_scope, cond_spec)
column_spec = cond_spec.last.is_a?(Hash) ? cond_spec : cond_spec.push({})
attr_name, *vals_and_or_dir, options = column_spec
Column.new(base_scope, attr_name, *vals_and_or_dir, **options)
end
end
end