module Symphonia class SymphoniaQueryNotRegistered < StandardError end class FilterNotFound < StandardError end class Query attr_reader :set_filter, :controller attr_writer :entity_scope, :filters attr_accessor :disable_search, :active_filters, :options, :default_filters attr_accessor :operators class_attribute :model # @param [nil, String] sort column_name:asc|desc # @param [Hash] filters # @param [nil, Array] columns def initialize(c = nil, sort: "", filters: {}, columns: nil, **options) if c ActiveSupport::Deprecation.warn("initialize allow only hash. #{c} - update your controller:") # # model.query.new sort: "default sort", filters: {default: filter}, columns: [columns, names] # @query.from_params params # end ActiveSupport::Deprecation.warn("default_order is deprecated, use just `sort`") if options[:default_order] @active_filters = {} @default_sort = sort @default_filters = filters @default_column_names = columns @options = options end # @param [Hash] params # @option params [String] :sort name of column, also can be name:direction # @option params [String] :direction ASC or DESC # @option params [Boolean] :set_filter if `true` apply filters def from_params(params) @sort_col, @sort_dir = params[:sort]&.split(":") @sort_dir ||= params[:direction] self.entity_scope = entity.like(@q) if (@q = params[:q].presence) setup_filters(params) if (@set_filter = params[:set_filter].to_boolean) setup_columns(params) end protected def setup_filters(params) available_filters.each do |filter| if (q = params[filter.name]) if q.blank? active_filters.delete(filter.name) else operators.delete(filter.name) active_filters[filter.name] = q.try(:downcase) || q if (o = params[:o]) operators[filter.name] = o[filter.name] end end end end end def setup_columns(params) if (c = params[:column_names]).present? self.column_names = c end end public def sort_column c, d = @default_sort.split(':') c ||= available_columns[:name]&.name @sort_table ||= SortableTable::SortTable.new(available_columns.collect { |_n, c| c.sort_definition }.compact, default_column: c&.to_sym, default_direction: d&.to_sym) @sort_column ||= @sort_table.sort_column(@sort_col, @sort_dir) rescue StandardError raise "Incorrect default column: '#{options[:default_sort]}'" end def search? disable_search.nil? && model.respond_to?(:like) end def activate_filter(name, value) @active_filters[name] ||= value.downcase end alias add_filter activate_filter def entity model end def column_names=(names) @column_names = (names.is_a?(Array) ? names : names.to_s.split('|')).map(&:to_sym) end def column_names @column_names ||= default_column_names end def columns(all = false) return available_columns.values if all || Array(column_names).blank? @columns ||= column_names.map { |c| available_columns[c] }.compact end def render_filters(&block) (available_filters | filters).each(&block) end def entity_scope @entity_scope || model end def filters setup_filters(default_filters) unless @set_filter @filters ||= available_filters.select do |filter| if (q = active_filters[filter.name]) operators && (filter.operator = operators[filter.name]) filter.value = q end end @filters end def scope opts = columns.each_with_object(includes: [], preload: [], joins: []) do |var, mem| mem[:includes].concat var.includes mem[:preload].concat var.preload mem[:joins].concat var.joins end x = entity_scope.includes(opts[:includes].uniq).preload(opts[:preload].uniq).joins(opts[:joins].uniq) filters.each do |filter| x = filter.apply(x) end x end def entities return @entities unless @entities.nil? order = sort_column.order @entities = scope.order(order.is_a?(String) && Arel.sql(order) || order) end def entity_count entities.reorder(nil).count end def summarize_columns columns.select(&:summarize?) end def to_partial_path 'symphonia/common/query' end # Serialize query to params # @param [FalseClass] force use applied filters, even that wasn't before # @return [Hash] def to_params(force: false) if @q { q: @q } elsif @set_filter || force filters.inject(set_filter: 1) do |(f, v), mem| mem[f.send(:form_field_name).to_sym] = v end else {} end end def inspect "#<#{self.class.name} set_filter=#{@set_filter} >" end def available_filters self.active_filters ||= {} self.operators ||= {} if @available_filters.nil? @available_filters = [] register_filters end @available_filters end private # @param [Symbol] filter_type # @param [Symphonia::ModelAttributes::Attribute] attribute # @param [Hash] options def register_filter(filter_type, attribute, options = {}) @available_filters ||= [] klass = "Symphonia::ModelFilters::#{filter_type.to_s.classify}Filter".safe_constantize return if klass.nil? filter = klass.new(attribute, self, options) yield filter if block_given? @available_filters << filter end # def default_filter # {} # end def register_filters model.registered_attributes.each do |_name, attribute| register_filter(attribute.filter, attribute) if attribute.filter end end def available_columns if @available_columns.nil? @available_columns = {} register_columns else @available_columns end @available_columns end def default_column_names if @default_column_names.nil? @default_column_names = [] available_columns.each { |k, v| v.default? && @default_column_names << k } end @default_column_names end def add_attribute_column(attribute, options = {}) @available_columns[attribute.name] = ::Symphonia::QueryColumns::AttributeColumn.new(attribute, self, options) end protected def session_key(name) [self.class.name.hash, name].join('-') end def register_columns model.registered_attributes.values.each do |attribute| add_attribute_column(attribute) end end end end