diff --git a/app/models/will_filter/filter.rb b/app/models/will_filter/filter.rb
index 40e8052a..a823b34b 100644
--- a/app/models/will_filter/filter.rb
+++ b/app/models/will_filter/filter.rb
@@ -26,34 +26,34 @@
# Table name: will_filter_filters
#
# id INTEGER not null, primary key
-# type varchar(255)
-# name varchar(255)
-# data text
-# user_id integer
-# model_class_name varchar(255)
-# created_at datetime
-# updated_at datetime
+# type varchar(255)
+# name varchar(255)
+# data text
+# user_id integer
+# model_class_name varchar(255)
+# created_at datetime
+# updated_at datetime
#
# Indexes
#
-# index_will_filter_filters_on_user_id (user_id)
+# index_will_filter_filters_on_user_id (user_id)
#
#++
module WillFilter
class Filter < ActiveRecord::Base
- self.table_name = :will_filter_filters
+ self.table_name = :will_filter_filters
attr_accessible :type, :name, :data, :user_id, :model_class_name
# set_table_name :will_filter_filters
serialize :data
before_save :prepare_save
after_find :process_find
-
+
JOIN_NAME_INDICATOR = '>'
#############################################################################
- # Basics
+ # Basics
#############################################################################
def initialize(model_class = nil)
super()
@@ -68,51 +68,51 @@ def initialize(model_class = nil)
def dup
super.tap {|ii| ii.conditions = self.conditions.dup}
end
-
+
def prepare_save
self.data = serialize_to_params
self.type = self.class.name
end
-
+
def process_find
@errors = {}
deserialize_from_params(self.data)
end
-
+
#############################################################################
- # Defaults
+ # Defaults
#############################################################################
def show_export_options?
WillFilter::Config.exporting_enabled?
end
-
+
def show_save_options?
WillFilter::Config.saving_enabled?
end
-
- def match
+
+ def match
@match ||= :all
end
-
- def key
+
+ def key
@key ||= ''
end
-
- def errors
+
+ def errors
@errors ||= {}
end
-
+
def format
@format ||= :html
end
-
+
def fields
@fields ||= []
end
-
+
def extra_params
@extra_params ||= {}
- end
+ end
#############################################################################
# a list of indexed fields where at least one of them has to be in a query
@@ -121,52 +121,52 @@ def extra_params
def required_condition_keys
[]
end
-
+
# For extra security, this method must be overloaded by the extending class.
def model_class
if WillFilter::Config.require_filter_extensions?
- raise WillFilter::FilterException.new("model_class method must be overloaded in the extending class.")
+ raise WillFilter::FilterException.new("model_class method must be overloaded in the extending class.")
end
if model_class_name.blank?
- raise WillFilter::FilterException.new("model_class_name was not specified.")
+ raise WillFilter::FilterException.new("model_class_name was not specified.")
end
@model_class ||= model_class_name.constantize
end
-
+
def table_name
model_class.table_name
end
-
+
def key=(new_key)
@key = new_key
end
-
+
def match=(new_match)
@match = new_match
end
-
+
#############################################################################
- # Inner Joins come in a form of
+ # Inner Joins come in a form of
# [[joining_model_name, column_name], [joining_model_name, column_name]]
#############################################################################
def inner_joins
[]
end
-
+
def model_columns
model_class.columns
end
-
+
def model_column_keys
model_columns.collect{|col| col.name.to_sym}
end
-
+
def contains_column?(key)
model_column_keys.index(key) != nil
end
-
+
def definition
@definition ||= begin
defs = {}
@@ -179,20 +179,20 @@ def definition
defs[:"#{join_class.to_s.underscore}.#{col.name.to_sym}"] = default_condition_definition_for(col.name, col.sql_type)
end
end
-
+
defs
end
end
def self.container_by_sql_type(type)
- raise WillFilter::FilterException.new("Unsupported data type #{type}") unless WillFilter::Config.data_types[type]
+ return [] unless WillFilter::Config.data_types[type]
WillFilter::Config.data_types[type]
end
def container_by_sql_type(type)
self.class.container_by_sql_type(type)
end
-
+
def default_condition_definition_for(name, sql_data_type)
type = sql_data_type.split(" ").first.split("(").first.downcase
containers = container_by_sql_type(type)
@@ -204,52 +204,52 @@ def default_condition_definition_for(name, sql_data_type)
operators[o] = c
end
end
-
+
if name == "id"
- operators[:is_filtered_by] = :filter_list
+ operators[:is_filtered_by] = :filter_list
elsif "_id" == name[-3..-1]
begin
name[0..-4].camelcase.constantize
- operators[:is_filtered_by] = :filter_list
- rescue
+ operators[:is_filtered_by] = :filter_list
+ rescue
end
end
-
+
operators
end
-
+
def sorted_operators(opers)
(WillFilter::Config.operator_order & opers.keys.collect{|o| o.to_s})
end
-
+
def first_sorted_operator(opers)
sorted_operators(opers).first.to_sym
end
-
+
def default_order
'id'
end
-
+
def order
@order ||= default_order
@order = default_order unless contains_column?(@order.to_sym)
@order
end
-
+
def default_order_type
'desc'
end
-
+
def order_type
@order_type ||= default_order_type
@order_type = default_order_type unless ['asc', 'desc'].include?(@order_type.to_s)
@order_type
end
-
+
def order_clause
"#{order} #{order_type}"
end
-
+
def order_model
@order_model ||= begin
order_parts = order.split('.')
@@ -258,52 +258,52 @@ def order_model
else
model_class_name
end
- end
+ end
end
def order_clause
@order_clause ||= begin
order_parts = order.split('.')
if order_parts.size > 1
- "#{order_parts.first.camelcase.constantize.table_name}.#{order_parts.last} #{order_type}"
+ "#{order_parts.first.camelcase.constantize.table_name}.#{order_parts.last} #{order_type}, #{order_parts.first.camelcase.constantize.table_name}.id asc"
else
- "#{model_class_name.constantize.table_name}.#{order_parts.first} #{order_type}"
+ "#{model_class_name.constantize.table_name}.#{order_parts.first} #{order_type}, #{model_class_name.constantize.table_name}.id asc"
end
- end
+ end
end
def column_sorted?(key)
key.to_s == order
end
-
+
def default_per_page
30
end
-
+
def per_page
@per_page ||= default_per_page
end
-
+
def page
@page ||= 1
end
-
+
def default_per_page_options
[10, 20, 30, 40, 50, 100]
end
-
+
def per_page_options
@per_page_options ||= default_per_page_options.collect{ |n| [n.to_s, n.to_s] }
end
-
+
def match_options
[["all", "all"], ["any", "any"]]
end
-
+
def order_type_options
[["desc", "desc"], ["asc", "asc"]]
end
-
+
#############################################################################
# Can be overloaded for custom titles
#############################################################################
@@ -314,10 +314,10 @@ def condition_title_for(key)
if title_parts.size > 1
"#{JOIN_NAME_INDICATOR} #{title}"
else
- title
+ title
end
end
-
+
def condition_options
@condition_options ||= begin
opts = []
@@ -325,13 +325,13 @@ def condition_options
opts << [condition_title_for(cond), cond.to_s]
end
opts = opts.sort_by{|opt| opt.first.gsub(JOIN_NAME_INDICATOR, 'zzz') }
-
+
separated = []
opts.each_with_index do |opt, index|
if index > 0
prev_opt_parts = opts[index-1].first.split(":")
curr_opt_parts = opt.first.split(":")
-
+
if (prev_opt_parts.size != curr_opt_parts.size) or (curr_opt_parts.size > 1 and (prev_opt_parts.first != curr_opt_parts.first))
key_parts = opt.last.split('.')
separated << ["-------------- #{curr_opt_parts.first.gsub("#{JOIN_NAME_INDICATOR} ", '')} --------------", "#{key_parts.first}.id"]
@@ -342,32 +342,32 @@ def condition_options
separated
end
end
-
+
def operator_options_for(condition_key)
condition_key = condition_key.to_sym if condition_key.is_a?(String)
-
+
opers = definition[condition_key]
raise WillFilter::FilterException.new("Invalid condition #{condition_key} for filter #{self.class.name}") unless opers
sorted_operators(opers).collect{|o| [o.to_s.gsub('_', ' '), o]}
end
-
+
# called by the list container, should be overloaded in a subclass
def value_options_for(condition_key)
[]
end
-
+
def container_for(condition_key, operator_key)
condition_key = condition_key.to_sym if condition_key.is_a?(String)
-
+
opers = definition[condition_key]
raise WillFilter::FilterException.new("Invalid condition #{condition_key} for filter #{self.class.name}") unless opers
oper = opers[operator_key]
-
+
# if invalid operator_key was passed, use first operator
oper = opers[first_sorted_operator(opers)] unless oper
oper
end
-
+
def conditions_for(condition_key)
@conditions.select{|cond| cond.key == condition_key}
end
@@ -393,80 +393,80 @@ def replace_condition(condition_key, operator_key, values = [])
remove_condition(condition_key)
add_condition_at(size, condition_key, operator_key, values)
end
-
+
def valid_operator?(condition_key, operator_key)
condition_key = condition_key.to_sym if condition_key.is_a?(String)
opers = definition[condition_key]
return false unless opers
opers[operator_key]!=nil
end
-
+
def add_condition_at(index, condition_key, operator_key, values = [])
values = [values] unless values.instance_of?(Array)
values = values.collect{|v| v.to_s}
-
+
condition_key = condition_key.to_sym if condition_key.is_a?(String)
-
+
unless valid_operator?(condition_key, operator_key)
opers = definition[condition_key]
operator_key = first_sorted_operator(opers)
end
-
+
condition = WillFilter::FilterCondition.new(self, condition_key, operator_key, container_for(condition_key, operator_key), values)
@conditions.insert(index, condition)
end
-
+
#############################################################################
# options always go in [NAME, KEY] format
#############################################################################
def default_condition_key
condition_options.first.last
end
-
+
#############################################################################
# options always go in [NAME, KEY] format
#############################################################################
def default_operator_key(condition_key)
operator_options_for(condition_key).first.last
end
-
- def conditions=(new_conditions)
+
+ def conditions=(new_conditions)
@conditions = new_conditions
end
-
+
def conditions
@conditions ||= []
end
-
+
def condition_at(index)
conditions[index]
end
-
+
def condition_by_key(key)
conditions.each do |c|
return c if c.key==key
end
nil
end
-
+
def size
conditions.size
end
-
+
def add_default_condition_at(index)
add_condition_at(index, default_condition_key, default_operator_key(default_condition_key))
end
-
+
def remove_condition_at(index)
conditions.delete_at(index)
end
-
+
def remove_all
@conditions = []
end
-
+
#############################################################################
- # Serialization
+ # Serialization
#############################################################################
def serialize_to_params(merge_params = {})
params = {}
@@ -478,12 +478,12 @@ def serialize_to_params(merge_params = {})
params[:wf_per_page] = per_page
params[:wf_export_fields] = fields.join(',')
params[:wf_export_format] = format
-
+
0.upto(size - 1) do |index|
condition = condition_at(index)
condition.serialize_to_params(params, index)
end
-
+
params.merge!(extra_params)
params.merge!(merge_params)
HashWithIndifferentAccess.new(params)
@@ -497,7 +497,7 @@ def to_url_params
end
params.join("&")
end
-
+
def to_s
to_url_params
end
@@ -514,15 +514,15 @@ def self.deserialize_from_params(params)
unless filter_instance.kind_of?(WillFilter::Filter)
raise WillFilter::FilterException.new("Invalid filter class. Filter classes must extand WillFilter::Filter.")
- end
-
+ end
+
if WillFilter::Config.require_filter_extensions?
- filter_instance.deserialize_from_params(params)
- else
- filter_class.new(params[:wf_model]).deserialize_from_params(params)
+ filter_instance.deserialize_from_params(params)
+ else
+ filter_class.new(params[:wf_model]).deserialize_from_params(params)
end
end
-
+
def deserialize_from_params(params)
params = HashWithIndifferentAccess.new(params) unless params.is_a?(HashWithIndifferentAccess)
@@ -531,28 +531,28 @@ def deserialize_from_params(params)
@key = params[:wf_key] || self.id.to_s
self.model_class_name = params[:wf_model] if params[:wf_model]
-
+
@per_page = params[:wf_per_page] || default_per_page
@page = params[:page] || 1
@order_type = params[:wf_order_type] || default_order_type
@order = params[:wf_order] || default_order
-
+
self.id = params[:wf_id].to_i unless params[:wf_id].blank?
self.name = params[:wf_name] unless params[:wf_name].blank?
-
+
@fields = []
unless params[:wf_export_fields].blank?
params[:wf_export_fields].split(",").each do |fld|
@fields << fld.to_sym
end
end
-
+
if params[:wf_export_format].blank?
@format = :html
- else
+ else
@format = params[:wf_export_format].to_sym
end
-
+
i = 0
while params["wf_c#{i}"] do
conditon_key = params["wf_c#{i}"]
@@ -566,7 +566,7 @@ def deserialize_from_params(params)
i += 1
add_condition(conditon_key, operator_key.to_sym, values)
end
-
+
if params[:wf_submitted] == 'true'
validate!
end
@@ -574,37 +574,37 @@ def deserialize_from_params(params)
if WillFilter::Config.user_filters_enabled? and WillFilter::Config.current_user
self.user_id = WillFilter::Config.current_user.id
end
-
+
self
end
alias_method :from_params, :deserialize_from_params
-
+
#############################################################################
- # Validations
+ # Validations
#############################################################################
def errors?
(@errors and @errors.size > 0)
end
-
+
def empty?
size == 0
end
-
+
def has_condition?(key)
condition_by_key(key) != nil
end
-
+
def valid_format?
WillFilter::Config.default_export_formats.include?(format.to_s)
end
-
+
def required_conditions_met?
return true if required_condition_keys.blank?
sconditions = conditions.collect{|c| c.key.to_s}
rconditions = required_condition_keys.collect{|c| c.to_s}
not (sconditions & rconditions).empty?
end
-
+
def validate!
@errors = {}
0.upto(size - 1) do |index|
@@ -612,21 +612,21 @@ def validate!
err = condition.validate
@errors[index] = err if err
end
-
+
unless required_conditions_met?
@errors[:filter] = "Filter must contain at least one of the following conditions: #{required_condition_keys.join(", ")}"
end
-
+
errors?
end
-
+
#############################################################################
- # SQL Conditions
+ # SQL Conditions
#############################################################################
def sql_conditions
@sql_conditions ||= begin
- if errors?
- [" 1 = 2 "]
+ if errors?
+ [" 1 = 2 "]
else
all_sql_conditions = [""]
0.upto(size - 1) do |index|
@@ -635,11 +635,11 @@ def sql_conditions
next unless condition.container
sql_condition = condition.container.sql_condition
-
+
unless sql_condition
raise WillFilter::FilterException.new("Unsupported operator #{condition.operator_key} for container #{condition.container.class.name}")
end
-
+
if all_sql_conditions[0].size > 0
all_sql_conditions[0] << ( match.to_sym == :all ? " AND " : " OR ")
end
@@ -656,41 +656,41 @@ def sql_conditions
end
end
end
-
+
def debug_conditions(conds)
all_conditions = []
conds.each_with_index do |c, i|
cond = ""
if i == 0
cond << "\"#{c}\""
- else
+ else
cond << "
#{i}) "
if c.is_a?(Array)
cond << "["
cond << (c.collect{|v| "\"#{v.strip}\""}.join(", "))
cond << "]"
- elsif c.is_a?(Date)
+ elsif c.is_a?(Date)
cond << "\"#{c.strftime("%Y-%m-%d")}\""
- elsif c.is_a?(Time)
+ elsif c.is_a?(Time)
cond << "\"#{c.strftime("%Y-%m-%d %H:%M:%S")}\""
- elsif c.is_a?(Integer)
+ elsif c.is_a?(Integer)
cond << c.to_s
- else
+ else
cond << "\"#{c}\""
end
end
-
+
all_conditions << cond
end
all_conditions.join("")
end
-
+
def debug_sql_conditions
debug_conditions(sql_conditions)
end
-
+
#############################################################################
- # Saved Filters
+ # Saved Filters
#############################################################################
def user_filters
@user_filters ||= begin
@@ -704,7 +704,7 @@ def user_filters
conditions << "0"
end
end
-
+
WillFilter::Filter.find(:all, :conditions => conditions)
end
end
@@ -712,21 +712,21 @@ def user_filters
def saved_filters(include_default = true)
@saved_filters ||= begin
filters = []
-
+
if include_default
filters = default_filters
if (filters.size > 0)
filters.insert(0, ["-- Select Default Filter --", "-1"])
end
end
-
+
if user_filters.any?
filters << ["-- Select Saved Filter --", "-2"] if include_default
user_filters.each do |filter|
filters << [filter.name, filter.id.to_s]
end
end
-
+
filters
end
end
@@ -737,53 +737,53 @@ def saved_filters(include_default = true)
def default_filter_if_empty
nil
end
-
+
def handle_empty_filter!
return unless empty?
return if default_filter_if_empty.nil?
load_filter!(default_filter_if_empty)
end
-
+
def default_filters
[]
end
-
+
def default_filter_conditions(key)
[]
end
-
+
def load_default_filter(key)
default_conditions = default_filter_conditions(key)
return if default_conditions.nil? or default_conditions.empty?
-
+
unless default_conditions.first.is_a?(Array)
add_condition(*default_conditions)
return
end
-
+
default_conditions.each do |default_condition|
add_condition(*default_condition)
end
end
-
+
def reset!
remove_all
@sql_conditions = nil
@results = nil
end
-
+
def load_filter!(key_or_id)
reset!
@key = key_or_id.to_s
-
+
load_default_filter(key)
return self unless empty?
-
+
filter = WillFilter::Filter.find_by_id(key_or_id.to_i)
raise WillFilter::FilterException.new("Invalid filter key #{key_or_id.to_s}") if filter.nil?
filter
end
-
+
#############################################################################
# Export Filter Data
#############################################################################
@@ -801,18 +801,18 @@ def export_formats
end
formats
end
-
+
def custom_format?
custom_formats.each do |frmt|
return true if frmt[1].to_sym == format
end
false
end
-
+
def custom_formats
[]
end
-
+
def process_custom_format
""
end
@@ -820,11 +820,11 @@ def process_custom_format
def association_name(inner_join)
(inner_join.is_a?(Array) ? inner_join.first : inner_join).to_sym
end
-
+
def association_class(inner_join)
model_class.new.association(association_name(inner_join)).build.class
- end
-
+ end
+
# deprecated for Rails 3.0 and up
def joins
return nil if inner_joins.empty?
@@ -860,26 +860,26 @@ def process_custom_conditions(objects)
filtered = []
objects.each do |obj|
condition_flags = []
-
+
0.upto(size - 1) do |index|
condition = condition_at(index)
next unless custom_condition?(condition)
condition_flags << custom_condition_met?(condition, obj)
end
-
+
if condition_flags.size > 0
next if match.to_s == "all" and condition_flags.include?(false)
- next unless condition_flags.include?(true)
- end
-
- filtered << obj
+ next unless condition_flags.include?(true)
+ end
+
+ filtered << obj
end
filtered
end
def results
@results ||= begin
- handle_empty_filter!
+ handle_empty_filter!
recs = model_class.where(sql_conditions).order(order_clause)
inner_joins.each do |inner_join|
recs = recs.joins(association_name(inner_join))
@@ -888,14 +888,14 @@ def results
if custom_conditions?
recs = process_custom_conditions(recs.all)
recs = Kaminari.paginate_array(recs)
- end
+ end
recs = recs.page(page).per(per_page)
recs.wf_filter = self
recs
end
end
-
+
# sums up the column for the given conditions
def sum(column_name)
model_class.sum(column_name, :conditions => sql_conditions)
@@ -918,4 +918,4 @@ def count(column_name)
end
end
-end
\ No newline at end of file
+end