kaminari--kaminari/kaminari-activerecord/lib/kaminari/activerecord/active_record_relation_meth...

96 lines
3.5 KiB
Ruby

# frozen_string_literal: true
module Kaminari
# Active Record specific page scope methods implementations
module ActiveRecordRelationMethods
# Used for page_entry_info
def entry_name(options = {})
default = options[:count] == 1 ? model_name.human : model_name.human.pluralize
model_name.human(options.reverse_merge(default: default))
end
def reset #:nodoc:
@total_count = nil
super
end
def total_count(column_name = :all, _options = nil) #:nodoc:
return @total_count if defined?(@total_count) && @total_count
# There are some cases that total count can be deduced from loaded records
if loaded?
# Total count has to be 0 if loaded records are 0
return @total_count = 0 if (current_page == 1) && @records.empty?
# Total count is calculable at the last page
return @total_count = (current_page - 1) * limit_value + @records.length if @records.any? && (@records.length < limit_value)
end
# #count overrides the #select which could include generated columns referenced in #order, so skip #order here, where it's irrelevant to the result anyway
c = except(:offset, :limit, :order)
# Remove includes only if they are irrelevant
c = c.except(:includes) unless references_eager_loaded_tables?
# .group returns an OrderedHash that responds to #count
c = c.count(column_name)
@total_count = if c.is_a?(Hash) || c.is_a?(ActiveSupport::OrderedHash)
c.count
elsif c.respond_to? :count
c.count(column_name)
else
c
end
end
# Turn this Relation to a "without count mode" Relation.
# Note that the "without count mode" is supposed to be performant but has a feature limitation.
# Pro: paginates without casting an extra SELECT COUNT query
# Con: unable to know the total number of records/pages
def without_count
extend ::Kaminari::PaginatableWithoutCount
end
end
# A module that makes AR::Relation paginatable without having to cast another SELECT COUNT query
module PaginatableWithoutCount
# Overwrite AR::Relation#load to actually load one more record to judge if the page has next page
# then store the result in @_has_next ivar
def load
if loaded? || limit_value.nil?
super
else
@values[:limit] = limit_value + 1
# FIXME: this could be removed when we're dropping AR 4 support
@arel.limit = @values[:limit] if @arel && (Integer === @arel.limit)
super
@values[:limit] = limit_value - 1
# FIXME: this could be removed when we're dropping AR 4 support
@arel.limit = @values[:limit] if @arel && (Integer === @arel.limit)
if @records.any?
@records = @records.dup if (frozen = @records.frozen?)
@_has_next = !!@records.delete_at(limit_value)
@records.freeze if frozen
end
self
end
end
# The page wouldn't be the last page if there's "limit + 1" record
def last_page?
!out_of_range? && !@_has_next
end
# Empty relation needs no pagination
def out_of_range?
load unless loaded?
@records.empty?
end
# Force to raise an exception if #total_count is called explicitly.
def total_count
raise "This scope is marked as a non-count paginable scope and can't be used in combination " \
"with `#paginate' or `#page_entries_info'. Use #link_to_next_page or #link_to_previous_page instead."
end
end
end