lib/listings/base.rb in listings-0.0.2 vs lib/listings/base.rb in listings-0.0.3
- old
+ new
@@ -1,94 +1,147 @@
+require 'listings/configuration_methods'
+require 'listings/view_helper_methods'
+require 'csv'
+
module Listings
- module ConfigurationMethods
- extend ActiveSupport::Concern
+ class Base
+ include Listings::ConfigurationMethods
+ include Listings::ViewHelperMethods
+ include ERB::Util # html_escape methods are private in the view_context
+ # so they need to be included in order to be available
- included do
- attr_accessor :page
- attr_accessor :scope
- attr_accessor :items
+ attr_accessor :view_context
+ attr_accessor :params
- def page_size
- self.class.page_size
- end
+ attr_accessor :search_criteria
+ attr_accessor :search_filters
- def scopes
- self.class.scopes
+ def initialize
+ @page_size = self.class.page_size
+ end
+
+ def name
+ self.class.name.underscore.sub(/_listing$/,'')
+ end
+
+ def param_scope; :scope; end
+ def param_page; :page; end
+ def param_search; :s; end
+ def param_sort_by; :sort_by; end
+ def param_sort_direction; :sort_d; end
+
+ def parse_search
+ if !filterable?
+ # if it is not filterable, all the search is used as search criteria
+ self.search_criteria = self.search
+ self.search_filters = {}
+ else
+ # otherwise parse the search stripping out allowed filterable fields
+ self.search_filters, self.search_criteria = parse_filter(self.search, self.filters)
end
+ end
- def model_class
- self.class.model_class
+ def parse_filter(text, filter_keys)
+ filters = {}
+ filter_keys.each do |key|
+ text = collect_filter text, key, filters
end
- def columns
- self.class.columns
+ return filters, text
+ end
+
+ def collect_filter(text, filter, filters_hash)
+ ["#{filter}:\s*(\\w+)",
+ "#{filter}:\s*\"([^\"]+)\"",
+ "#{filter}:\s*\'([^\']+)\'"].each do |pattern|
+ m = Regexp.new(pattern, Regexp::IGNORECASE).match(text)
+ if m
+ filters_hash[filter] = m[1]
+ return "#{m.pre_match.strip} #{m.post_match.strip}".strip
+ end
end
+
+ return text
end
- module ClassMethods
- attr_accessor :page_size
- attr_accessor :model_class
+ def filter_items(params, items)
+ self.page = params[param_page] || 1
+ self.scope = scope_by_name(params[param_scope])
+ self.search = params[param_search]
+ parse_search
- def paginates_per(val)
- @page_size = val
- end
+ items = paginatable(scope.apply(self, items)) unless scope.nil?
- def scope(name, props = {})
- scopes << ScopeDescriptor.new(name, props)
+ if search_criteria.present? && self.searchable?
+ criteria = []
+ values = []
+ self.columns.select(&:searchable?).each do |col|
+ criteria << "#{model_class.table_name}.#{col.name} like ?"
+ values << "%#{search_criteria}%"
+ end
+ items = items.where(criteria.join(' or '), *values)
end
- def scopes
- @scopes ||= []
+ if filterable?
+ # pluck filters values before applying filters/pagination/sorting
+ self.filter_values = {}
+ filters.each do |v|
+ self.filter_values[v] = items.pluck("distinct #{v}").reject(&:nil?)
+ end
+
+ self.search_filters.each do |key, filter_value|
+ items = items.where("#{model_class.table_name}.#{key} = ?", filter_value)
+ end
end
- def model(val)
- @model_class = val
+ if params.include?(param_sort_by)
+ sort_col = column_with_name(params[param_sort_by])
+ sort_col.sort = params[param_sort_direction]
+ items = items.reorder("#{sort_col.sort_by} #{params[param_sort_direction]}")
end
- def columns
- @columns ||= []
+ if paginated?
+ items = items.page(page).per(page_size)
end
- def column(name = '', &proc)
- columns << ColumnDescriptor.new(self, name, proc)
+ if items.is_a?(Class)
+ items = items.all
end
+ items
end
- end
- module ViewHelperMethods
- extend ActiveSupport::Concern
+ def query_items(params)
+ @params = params
+ items = self.model_class
+ @has_active_model_source = items.respond_to? :human_attribute_name
- included do
- def is_active_scope(scope)
- self.scope.name == scope.name
- end
+ self.items = filter_items(self.scoped_params, paginatable(items))
+ end
- def url_for_scope(scope)
- params = view_context.params.merge(param_scope => scope.name)
- params.delete param_page
- view_context.url_for(params)
+ def paginatable(array_or_model)
+ if array_or_model.is_a?(Array) && paginated? && !array_or_model.respond_to?(:page)
+ Kaminari.paginate_array(array_or_model)
+ else
+ array_or_model
end
end
- end
- class Base
- include Listings::ConfigurationMethods
- include Listings::ViewHelperMethods
- attr_accessor :view_context
+ def has_active_model_source?
+ @has_active_model_source
+ end
- def param_scope; :scope; end
- def param_page; :page; end
+ def selectable?
+ self.class.selectable?
+ end
- def query_items(params)
- self.page = params[param_page] || 1
- self.scope = scope_by_name(params[param_scope])
+ def paginated?
+ self.page_size != :none
+ end
- items = self.model_class
- items = scope.apply(items) unless scope.nil?
-
- self.items = items.page(page).per(page_size)
+ def scoped_params
+ @params.except(:listing, :controller, :action)
end
def scope_by_name(name)
default_scope = scopes.find { |s| s.is_default? }
selected_scope = scopes.find { |s| s.name == name.try(:to_sym) }
@@ -97,64 +150,71 @@
def value_for(column, item)
column.value_for(self, item)
end
- def method_missing(m, *args, &block)
- delegated_to = view_context.respond_to?(m) ? view_context : view_context.main_app
- delegated_to.send(m, *args, block)
+ def column_with_name(name)
+ self.columns.find { |c| c.name.to_s == name.to_s }
end
- end
- class ScopeDescriptor
- attr_accessor :name
+ def searchable?
+ self.columns.any? &:searchable?
+ end
- def initialize(name, props = {})
- props.reverse_merge! default: false
+ def filterable?
+ !self.filters.empty?
+ end
- @name = name
- @props = props
+ def selectable_id(model)
+ model.id
end
- def human_name
- name.capitalize
+ def url
+ view_context.listings.listing_full_path(self.name, self.params)
end
- def is_default?
- @props[:default]
+ def search_data
+ { criteria: self.search_criteria, filters: self.search_filters }
end
- def apply(items)
- if name == :all
- items
- else
- items.send(name)
- end
+ def format
+ (params[:format] || :html).to_sym
end
- end
- class ColumnDescriptor
- attr_reader :name
+ def to_array
+ data = []
- def initialize(listing_class, name, proc)
- @listing_class = listing_class
- @name = name
- @proc = proc
+ data << self.columns.map { |c| c.human_name }
+
+ self.items.each do |item|
+ row = []
+ self.columns.each do |col|
+ row << col.value_for(item)
+ end
+ data << row
+ end
+
+ data
end
- def value_for(listing, model)
- if @proc
- listing.instance_exec model, &@proc
- else
- model.send(name)
+ def to_csv
+ CSV.generate do |csv|
+ self.to_array.each do |row|
+ csv << row
+ end
end
end
- def human_name
- if name.is_a? Symbol
- @listing_class.model_class.human_attribute_name(name)
+ def method_missing(m, *args, &block)
+ view_context.send(m, *args, &block)
+ end
+
+ def kaminari_theme
+ case Listings.configuration.theme
+ when 'twitter-bootstrap-2'
+ 'twitter-bootstrap'
else
- name
+ Listings.configuration.theme
end
end
end
-end
\ No newline at end of file
+end