gregbell/active_admin

View on GitHub
lib/active_admin/views/components/paginated_collection.rb

Summary

Maintainability
A
3 hrs
Test Coverage
# frozen_string_literal: true
module ActiveAdmin
  module Views
    # Wraps the content with pagination and available formats.
    #
    # *Example:*
    #
    #   paginated_collection collection, entry_name: "Post" do
    #     div do
    #       h2 "Inside the
    #     end
    #   end
    #
    # This will create a div with a sentence describing the number of
    # posts in one of the following formats:
    #
    # * "No Posts found"
    # * "Showing all 10 Posts"
    # * "Showing Posts 1 - 30 of 31 in total"
    #
    # It will also generate pagination links.
    #
    class PaginatedCollection < ActiveAdmin::Component
      builder_method :paginated_collection

      attr_reader :collection

      # Builds a new paginated collection component
      #
      # collection => A paginated collection from kaminari
      # options    => These options will be passed to `page_entries_info`
      #   entry_name     => The name to display for this resource collection
      #   params         => Extra parameters for pagination (e.g. { anchor: 'details' })
      #   param_name     => Parameter name for page number in the links (:page by default)
      #   download_links => Download links override (false or [:csv, :pdf])
      #
      def build(collection, options = {})
        @collection = collection
        @params = options.delete(:params)
        @param_name = options.delete(:param_name)
        @download_links = options.delete(:download_links)
        @display_total = options.delete(:pagination_total) { true }
        @per_page = options.delete(:per_page)

        unless @collection.respond_to?(:total_pages)
          raise(StandardError, "Collection is not a paginated scope. Set collection.page(params[:page]).per(10) before calling :paginated_collection.")
        end
        add_class "paginated-collection"
        @contents = div(class: "paginated-collection-contents")
        build_pagination_with_formats(options)
        @built = true
      end

      # Override add_child to insert all children into the @contents div
      def add_child(*args, &block)
        if @built
          @contents.add_child(*args, &block)
        else
          super
        end
      end

      protected

      def build_pagination_with_formats(options)
        div class: "paginated-collection-pagination" do
          div page_entries_info(options).html_safe, class: "pagination-information"
          build_pagination
        end
        formats = build_download_formats @download_links
        if @per_page.is_a?(Array) || formats.any?
          div class: "paginated-collection-footer" do
            build_per_page_select if @per_page.is_a?(Array)
            render("active_admin/shared/download_format_links", formats: formats) if formats.any?
          end
        end
      end

      def build_per_page_select
        div do
          text_node I18n.t("active_admin.pagination.per_page")
          select class: "pagination-per-page" do
            @per_page.each do |per_page|
              option(
                per_page,
                value: per_page,
                selected: @collection.limit_value == per_page ? "selected" : nil
              )
            end
          end
        end
      end

      def build_pagination
        options = { views_prefix: :active_admin, outer_window: 1, window: 2 }
        options[:params] = @params if @params
        options[:param_name] = @param_name if @param_name

        if !@display_total
          # The #paginate method in kaminari will query the resource with a
          # count(*) to determine how many pages there should be unless
          # you pass in the :total_pages option. We issue a query to determine
          # if there is another page or not, but the limit/offset make this
          # query fast.
          offset = @collection.offset(@collection.current_page * @collection.limit_value).limit(1).count
          options[:total_pages] = @collection.current_page + offset
          options[:right] = 0
        end

        text_node paginate @collection, **options
      end

      # modified from will_paginate
      def page_entries_info(options = {})
        if options[:entry_name]
          entry_name = options[:entry_name]
          entries_name = options[:entries_name] || entry_name.pluralize
        elsif collection_empty?(@collection)
          entry_name = I18n.t "active_admin.pagination.entry", count: 1, default: "entry"
          entries_name = I18n.t "active_admin.pagination.entry", count: 2, default: "entries"
        else
          key = "activerecord.models." + @collection.first.class.model_name.i18n_key.to_s

          entry_name = I18n.translate key, count: 1, default: @collection.first.class.name.underscore.sub("_", " ")
          entries_name = I18n.translate key, count: @collection.size, default: entry_name.pluralize
        end

        if @display_total
          if @collection.total_pages < 2
            case collection_size(@collection)
            when 0; I18n.t("active_admin.pagination.empty", model: entries_name)
            when 1; I18n.t("active_admin.pagination.one", model: entry_name)
            else; I18n.t("active_admin.pagination.one_page", model: entries_name, n: @collection.total_count)
            end
          else
            offset = (@collection.current_page - 1) * @collection.limit_value
            total = @collection.total_count
            I18n.t "active_admin.pagination.multiple",
                   model: entries_name,
                   total: total,
                   from: offset + 1,
                   to: offset + collection_size(@collection)
          end
        else
          # Do not display total count, in order to prevent a `SELECT count(*)`.
          # To do so we must not call `@collection.total_pages`
          offset = (@collection.current_page - 1) * @collection.limit_value
          I18n.t "active_admin.pagination.multiple_without_total",
                 model: entries_name,
                 from: offset + 1,
                 to: offset + collection_size(@collection)
        end
      end
    end
  end
end