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