require "active_support/core_ext/class/attribute" module Datagrid module Filters require "datagrid/filters/base_filter" require "datagrid/filters/enum_filter" require "datagrid/filters/boolean_enum_filter" require "datagrid/filters/extended_boolean_filter" require "datagrid/filters/boolean_filter" require "datagrid/filters/date_filter" require "datagrid/filters/date_time_filter" require "datagrid/filters/default_filter" require "datagrid/filters/integer_filter" require "datagrid/filters/composite_filters" require "datagrid/filters/string_filter" require "datagrid/filters/float_filter" require "datagrid/filters/dynamic_filter" FILTER_TYPES = { :date => Filters::DateFilter, :datetime => Filters::DateTimeFilter, :string => Filters::StringFilter, :default => Filters::DefaultFilter, :eboolean => Filters::BooleanEnumFilter , :xboolean => Filters::ExtendedBooleanFilter , :boolean => Filters::BooleanFilter , :integer => Filters::IntegerFilter, :enum => Filters::EnumFilter, :float => Filters::FloatFilter, :dynamic => Filters::DynamicFilter } class DefaultFilterScope end def self.included(base) #:nodoc: base.extend ClassMethods base.class_eval do include Datagrid::Core include Datagrid::Filters::CompositeFilters class_attribute :filters_array self.filters_array = [] end base.send :include, InstanceMethods end # self.included module ClassMethods # Returns filter definition object by name def filter_by_name(attribute) return attribute if attribute.is_a?(Datagrid::Filters::BaseFilter) self.filters.find do |filter| filter.name.to_sym == attribute.to_sym end end # Defines new datagrid filter. # This method automatically generates attr_accessor for filter name # and adds it to the list of datagrid attributes. # # Arguments: # # * name - filter name # * type - filter type that defines type case and GUI representation of a filter # * options - hash of options # * block - proc to apply the filter # # Available options: # # * :header - determines the header of the filter # * :default - the default filter value. Able to accept a Proc in case default should be recalculated # * :multiple - if true multiple values can be assigned to this filter. # By default multiple values are parsed from string using `,` separator. # But you can specify a different separator as option value. Default: false. # * :allow_nil - determines if the value can be nil # * :allow_blank - determines if the value can be blank # * :before - determines the position of this filter, # by adding it before the filter passed here (when using datagrid_form_for helper) # * :after - determines the position of this filter, # by adding it after the filter passed here (when using datagrid_form_for helper) # * :dummy - if true, this filter will not be applied automatically # and will be just displayed in form. In case you may want to apply it manually. # * :if - specify the condition when the filter can be dislayed and used. # Accepts a block or a symbol with an instance method name # * :unless - specify the reverse condition when the filter can be dislayed and used. # Accepts a block or a symbol with an instance method name # # See: https://github.com/bogdan/datagrid/wiki/Filters for examples def filter(name, type = :default, options = {}, &block) if type.is_a?(Hash) options = type type = :default end klass = type.is_a?(Class) ? type : FILTER_TYPES[type] raise ConfigurationError, "filter class #{type.inspect} not found" unless klass position = Datagrid::Utils.extract_position_from_options(filters_array, options) filter = klass.new(self, name, options, &block) filters_array.insert(position, filter) datagrid_attribute(name) do |value| filter.parse_values(value) end end def default_filter DefaultFilterScope.new end def inspect "#{super}(#{filters_inspection})" end def filters filters_array end protected def inherited(child_class) super(child_class) child_class.filters_array = self.filters_array.clone end def filters_inspection return "no filters" if filters.empty? filters.map do |filter| "#{filter.name}: #{filter.type}" end.join(", ") end end # ClassMethods module InstanceMethods def initialize(*args, &block) # :nodoc: self.filters_array = self.class.filters_array.clone self.filters_array.each do |filter| self[filter.name] = filter.default(self) end super(*args, &block) end def assets # :nodoc: apply_filters(super, filters) end # Returns filter value for given filter definition def filter_value(filter) self[filter.name] end # Returns string representation of filter value def filter_value_as_string(name) filter = filter_by_name(name) value = filter_value(filter) if value.is_a?(Array) value.map {|v| filter.format(v) }.join(filter.separator) else filter.format(value) end end # Returns filter object with the given name def filter_by_name(name) self.class.filter_by_name(name) end # Returns assets filtered only by specified filters # Allows partial filtering def filter_by(*filters) apply_filters(scope, filters.map{|f| filter_by_name(f)}) end # Returns select options for specific filter or filter name # If given filter doesn't support select options raises `ArgumentError` def select_options(filter) filter = filter_by_name(filter) unless filter.class.included_modules.include?(::Datagrid::Filters::SelectOptions) raise ::Datagrid::ArgumentError, "#{filter.name} with type #{FILTER_TYPES.invert[filter.class].inspect} can not have select options" end filter.select(self) end def default_filter self.class.default_filter end # Returns all currently enabled filters def filters self.class.filters.select do |filter| filter.enabled?(self) end end protected def apply_filters(current_scope, filters) filters.inject(current_scope) do |result, filter| filter.apply(self, result, filter_value(filter)) end end end # InstanceMethods end end