lib/magic_grid/collection.rb
require 'active_support/core_ext'
require 'magic_grid/logger'
module MagicGrid
class Collection
DEFAULTS = {
:per_page => 30,
:searchable => [],
:search_method => :search,
:listener_handler => nil,
:default_col => 0,
:post_filter => false,
:collection_post_filter => true,
:count => nil,
}
def initialize(collection, opts = {})
@collection = collection || []
@options = opts
@current_page = 1
@sorts = []
@filter_callbacks = []
@filters = []
@searches = []
@post_filters = []
@post_filter_callbacks = []
@paginations = []
@searchable_columns = []
@@kaminari_class = defined?(Kaminari) ? Kaminari : nil
end
delegate :quoted_table_name, :map, :count, :to => :collection
attr_accessor :searchable_columns
attr_reader :current_page, :original_count, :total_pages, :per_page,
:searches
attr_writer :options
cattr_accessor :kaminari_class
def options
DEFAULTS.merge(@options || {})
end
def count_options
options[:count]
end
def self.create_or_reuse(collection, opts = {})
if collection.is_a?(self)
collection.options = opts
collection
else
Collection.new(collection, opts)
end
end
def column_names
@collection.table.columns.map{|c| c[:name]}
rescue
MagicGrid.logger.debug("Given collection doesn't respond to #table well: #{$!}")
[]
end
def quote_column_name(col)
if col.is_a? Symbol and @collection.respond_to? :quoted_table_name
"#{quoted_table_name}.#{@collection.connection.quote_column_name(col.to_s)}"
else
col.to_s
end
end
def hash_string
if @collection.respond_to? :to_sql
@collection.to_sql.hash.abs.to_s(36)
else
options.hash.abs.to_s(36)
end
end
def search_using_builtin(collection, q)
collection.__send__(options[:search_method], q)
end
def search_using_where(collection, q)
result = collection
unless searchable_columns.empty?
begin
search_cols = searchable_columns.map {|c| c.custom_sql || c.name }
clauses = search_cols.map {|c| c << " LIKE :search" }.join(" OR ")
result = collection.where(clauses, {:search => "%#{q}%"})
rescue
MagicGrid.logger.debug "Given collection doesn't respond to :where well"
end
end
result
end
def sortable?
@collection.respond_to?(:order)
end
def apply_sort(col, dir)
if sortable? and col.sortable?
@reduced_collection = nil
@sorts << "#{col.custom_sql} #{dir}"
end
end
def searchable?
(filterable? and not searchable_columns.empty?) or
(options[:search_method] and
@collection.respond_to? options[:search_method])
end
def apply_search(q)
if q and not q.empty?
if searchable?
@reduced_collection = nil
@searches << q
else
MagicGrid.logger.warn "#{self.class.name}: Ignoring searchable fields on collection"
end
end
end
def perform_search(collection, q)
search_using_builtin(collection, q)
rescue
MagicGrid.logger.debug "Given collection doesn't respond to #{options[:search_method]} well"
search_using_where(collection, q)
end
def filterable?
@collection.respond_to? :where
end
def apply_filter(filters = {})
if filterable? and not filters.empty?
@reduced_collection = nil
@filters << filters
end
end
def apply_filter_callback(callback)
if callback.respond_to? :call
@reduced_collection = nil
@filter_callbacks << callback
end
end
def add_post_filter_callback(callback)
if callback.respond_to? :call
@reduced_collection = nil
@post_filter_callbacks << callback
end
end
def has_post_filter?
@collection.respond_to? :post_filter
end
def enable_post_filter(yes = true)
@reduced_collection = nil
if yes and has_post_filter?
@post_filters << :post_filter
end
self
end
def count(collection = nil)
count_or_hash = collection || @collection
while count_or_hash.respond_to? :count
count_or_hash = count_or_hash.send :count, *(Array([count_options]).compact)
end
count_or_hash
end
def per_page=(n)
@original_count = self.count @collection
@per_page = n
if @per_page
@total_pages = calculate_total_pages(@per_page, @original_count)
else
@total_pages = 1
end
end
def apply_pagination(current_page)
@current_page = current_page
@reduced_collection = nil
end
def default_paginate(collection, page, per_page)
collection = collection.to_enum
collection = collection.each_slice(@per_page)
collection = collection.drop(@current_page - 1)
collection = collection.first.to_a
class << collection
attr_accessor :current_page, :total_pages, :original_count
end
collection
end
def perform_pagination(collection)
return collection unless @per_page
total_entries = count(collection)
@current_page = bound_current_page(@current_page,
@per_page,
total_entries)
if collection.respond_to? :paginate
collection.paginate(:page => @current_page,
:per_page => @per_page,
:total_entries => total_entries)
elsif collection.respond_to? :page
collection.page(@current_page).per(@per_page)
elsif collection.is_a?(Array) and @@kaminari_class
@@kaminari_class.paginate_array(collection).
page(@current_page).per(@per_page)
else
default_paginate(collection, @current_page, @per_page)
end
end
def apply_all_operations(collection)
@sorts.each do |ordering|
collection = collection.order(ordering)
end
if @filter_callbacks.empty?
@filters.each do |hsh|
collection = collection.where(hsh)
end
else
@filter_callbacks.each do |callback|
collection = callback.call(collection)
end
end
@searches.each do |query|
collection = perform_search(collection, query)
end
# Do collection filter first, may convert from AR to Array
@post_filters.each do |filter|
collection = collection.__send__(filter)
end
@post_filter_callbacks.each do |callback|
collection = callback.call(collection)
end
# Paginate at the very end, after all sorting, filtering, etc..
perform_pagination(collection)
end
def collection
@reduced_collection ||= apply_all_operations(@collection)
end
def calculate_total_pages(per_page, total_entries)
pages = total_entries / per_page
pages += 1 if total_entries % per_page > 0
if pages < 1
1
else
pages
end
end
def bound_current_page(page, per_page, total_entries)
pages = calculate_total_pages(per_page, total_entries)
if page < 1
1
elsif page > pages
pages
else
page
end
end
end
end